00:00:12.950
All righty, hi everyone! I think we're good to start going. However, before we get going, it seems a little silly to do my presentation like this. So just hold on one moment let me get my slides.
00:00:26.340
Here on this floppy disk, I'm gonna go ahead and pop that in. I've got to move this video cable over. Wait for it to load up. Awesome! Turn this sucker on now.
00:00:35.370
If we go ahead and run the catalog command to get a list of everything on the disk, got my slides there! So I'll run the binary run command and execute the program with my slides. Now it's going to take a little bit because it has to load a whole eight kilobytes of slides into memory. The actual program, which I wrote in assembly to present these slides, is 241 bytes long. That is smaller than a single slide in Keynote and smaller than everyone's GIF in every single other talk today.
00:00:53.609
Now if you've written assembly, 241 bytes may actually seem kind of big. That's because I couldn't help myself; there was a little bit of feature bloat added with a lot of features. That's why it's a whole 241 bytes. All right, so a little bit about myself. My name is Colin Fulton, and I'm a software developer at a company called Duo Security, which is a cloud security company. It's a fantastic place to work, and I absolutely love working at Duo. We are currently hiring, so if you are interested in doing nothing at all related to this but doing cloud security, feel free to talk to me afterwards.
00:01:27.030
Now, to answer the elephant in the room: yes, this is an Apple IIc up here on stage with me. The Apple IIc is one of the later variants of the Apple II. It's the compact version; the original Apple II and indeed all Apple II models run on one megahertz CPUs—a whole megahertz of processor power. The original Apple II, the lowest model, came in with eight kilobytes of RAM, which included video memory! So there really isn't that much you could do with the original. Later versions like this one added a lot more RAM. Yes, this computer is, in fact, a couple of months older than me.
00:02:14.300
Some of you are probably thinking, 'Seriously, did you actually fly out here with all of this vintage computing stuff? Isn't this like dangerous to fly with all this delicate computing stuff?' You're totally right! It would be ridiculous of me to fly out here with one Apple IIc, which is why I flew out here with two Apple IIcs in case one broke. They loved me in security! You may also be wondering: couldn't you just use an emulator? Wouldn't that be good enough? Well, I could use an emulator to give this talk, but it would be nowhere near as fun and also nowhere near as dangerous.
00:02:44.570
So, the Apple IIc has a processor known as the 6502 processor. This processor isn't as commonly used today, though it is still produced. However, this is arguably the processor that really started the personal computer revolution. It didn't start the revolution because it was fast or particularly powerful; it's an 8-bit CPU with 16-bit addressing, and you have to do a little bit of weird programming to deal with the fact that it can only process eight bits at a time while addresses are longer than the data that can actually be processed. You might see a little bit of that later. It only has three registers, so if you're writing anything like assembly programs, that means you're effectively coding with only three variables. Whatever you do, you have to juggle all your data around in those three variables.
00:03:43.800
The 6502 is even more limited because those variables are all specialized—there is no single variable that can do all commands. Unlike modern CPUs, in contrast, the x86 processor inside this more modern Apple laptop in front of me has 16 general-purpose registers that can do all sorts of things. In fact, the x86 processor in here has a thousand instructions that it can run, ranging from relatively simple math operations to crazy complicated video rendering tasks.
00:04:01.350
For those of you interested, there is an interesting fact: because of your limited three registers, the creator of the 6502 gave you a special first page in memory. The first 256 bytes of memory have special commands for accessing that part of memory which run faster than any other memory commands. So, even though you only have three registers, you do have a special segment in memory you can use like virtual registers instead. In total, there are only 56 instructions on the 6502 processor, which we will get to in a moment.
00:04:30.250
That’s relatively limited in terms of what this computer can do if you are directly writing machine code. There is no multiply instruction, no divide instruction, and no subtract instruction. However, there is an add instruction, so you are like a quarter of the way to being able to do elementary school math. The reason why I say it only sort of has 56 instructions is because the rotate right instruction was originally broken on the first release of the 6502 processor, so they just didn’t mention it in any documentation. A little while later, they released an updated version of the 6502 with a rotate right instruction. How fancy! They just fixed the bug internally, and that’s how they got it up to 56 instructions.
00:05:05.540
The reason why the 6502 was used in the Apple II, as well as the Commodore 64, the Nintendo Entertainment System, and dozens of other personal computers at the very start of the personal computer revolution, is because it was relatively fast. It couldn’t do much, but what it did, it did quickly and it was cheap. When it initially came out, it was actually one-sixth the price of all the competing CPUs out there, which really annoyed a lot of competitors like Intel and Motorola. As a result, they ended up having to slash the price of their CPUs to try and match what the 6502 could do.
00:06:07.670
So, what is it like to program on this relatively limited machine? Well, I’d like to do a Hello World program, but do it in the same way that Steve Wozniak wrote the original BASIC interpreter. To do this, we'll do it entirely in machine code with only a very basic monitor program to directly manipulate memory. If it's the year 1977, you probably don't have a computer available to do a lot of your development work because, well, you know, cheap computers haven’t really been invented yet.
00:06:30.680
So the first thing you would do is get out a pencil and paper and plan out what you want the computer to do, and think through it ahead of time using the old fashioned way. If we want to print something to the screen, there are several different ways to do this. The algorithm I’m going to use here is that first we’ll initialize one of the registers inside the memory with the length of the string we want to print. We’ll then load a character from the string from memory. We can store our string somewhere in memory, we’ll load one of those characters into another register, and then go ahead and store that character from that register into video memory. We can directly write characters to the screen by writing to RAM.
00:07:38.120
This is not necessarily a faster way of doing it, but there are other subroutines inside that will do this in a slightly easier way. It’s the fourth thing that we need to do: we're going to decrement that register holding the string length since we've just printed one character. Now we need to check if we're at the end of the string. The next thing we’re going to do is check if we are at the end of the string. We’ll branch back to the earlier step, and if we aren’t at the end of the string and haven’t reached a string length of zero yet, we’re going back to step two to continue printing characters onto the screen until we reach the end.
00:08:11.619
Finally, when the string is completely printed, we can return from our program. So now we've got our plan for what the code should look like. What you would do at this point is get out a book or pamphlet that lists all the various machine code instructions with their hexadecimal values, and you would write those to memory. Steve Wozniak actually created a program called the Mini Monitor, embedded in all Apple IIs after the initial release, which lets you directly edit memory and is what he used to implement the first version of BASIC.
00:08:50.890
So here I am entering the Mini Monitor using the M key, which lets me access the prompt. What can we do in here? Well, we can read bytes from memory. If I type in the character 9, it tells me that the ninth byte in memory is 01. Awesome! We can also read ranges—if I say from ten to twenty, it will read out that range of memory and we can check it that way. How do we write to it? We will start our program by writing at the 800th byte, and the colon lets the Mini Monitor know that everything after this will be hexadecimal values that we want to write starting at the 800th byte in memory.
00:09:34.780
Now, the first thing we wanted to do was load the Y register with the length of the string. The load Y with a literal value instruction is zero, and then the length of the string in this case would be 0C. Next, we want to load the A register with a value directly from memory, wherever we’re storing our string, offset by the value in Y. The load from memory with an offset of Y into the A register is a straightforward instruction.
00:10:06.890
It has a better name than that. However, it’s luckily very easy to write: it’s just B9. Then we need the address in memory we’ll be reading from—a specific location like 0B08, using little-endian format. Now, we get to the exciting part of writing the rest of the program.
00:10:32.870
So I’m first going to type in 99, then 33, then 06, then 88, then D0, and finally F7. Obviously, we need to terminate this program, so we are going to type in 60. Now we get to the fun part, and if any of you know the character encoding in here, no spoilers. We’ll type in 40, then 49, then 60. Relatively uninteresting, but then we get to the fun part, so we are going to type in 50 to 55, which represent the letters in ‘Hello’.
00:11:14.670
So we are writing in 42, then 59, and the next part I think everyone can guess: that you got your 43, your 4F, your 4E, your 46, and then we get a little bit spicy and exciting with 61! I assume the captioning right now just looks glorious. All right, now we have written our program. We’re gonna hit enter, and we’ve now written all of that to memory. To run this program, I will first clear the screen a little bit to make room.
00:11:47.550
If anyone knows a better way to do this, please let me know. To run the program, we need to tell the Mini Monitor where we want to start in memory and then type in to start at 800, which is the beginning of our program. The G character then tells it to execute starting at that line. Now, if we did everything right... Hey! You have no idea how seldom I screw this up. I lie; I screw this up very often.
00:12:12.030
All right! So we have a program. The Mini Monitor also lets us disassemble the program to read in a more human-readable format. We can say, '800 L' and look at our program. It looks kind of like a confusing jumble of stuff, but if we look right here on the left-hand side, we have a column of addresses in memory, followed by all the bytes in those addresses and the instructions we wrote. So we initially loaded Y with a hexadecimal value of C and then loaded the A register with whatever was in this address offset by Y.
00:12:51.930
Then, we stored A in this address in memory offset by Y, etc., etc., etc. This is what it was actually like for Steve Wozniak to write the original BASIC interpreter. How he managed to do this for the original BASIC interpreter for the Apple II, I don’t know, but somehow he did, and it has surprisingly few bugs in it.
00:13:33.390
So, now I need to go back to my presentation. I’m going to go ahead and exit into BASIC and then enter 16, and we’re back where we were in the talk. For those of you who are curious, there isn’t actually an operating system on here. It isn’t that multiple applications are running concurrently; instead, we’re just handing off execution to different parts of memory.
00:14:02.780
I had to structure this talk so that none of these programs I’m running would overwrite each other, which was a fun juggling act! If this were how everyone had to do programming, I don’t think most of us would be here today. Most people would have given up on programming. Computers would be nowhere near as powerful as they are today. The BASIC programming language, that first language that Steve Wozniak implemented for the Apple II, is what kind of democratized programming.
00:14:56.980
It’s the programming language that got a lot of people interested in programming; it was many people's first programming language because, when you turned on this machine, it didn’t have anything except for a BASIC interpreter for you to interact with. Practically, a lot of people remember BASIC fondly, but it’s not the greatest programming language in the world. While this machine does have Steve Wozniak's original BASIC in it, the default one that it goes to is known as AppleSoft BASIC, which Apple bought from Microsoft and which was written by a guy named Bill Gates or something.
00:15:45.330
What was it like to program in the BASIC programming language on the Apple II? Well, let’s go ahead and access the Mini Monitor again and go back to the BASIC prompt. We can do fun things like print hello, which prints out the word 'hello'. We can create variables, for example, let’s say Ruby equals 'great', and it returns a type mismatch error. The reason for that is that you can’t have strings as variables because they’re stored in a different place in memory.
00:16:08.480
The language is a little bit lazy in how it works, thus requiring you to append a dollar sign to the end of any variable storing a string. So if we type in Ruby$ = 'great', we can then print Ruby$, which will print out 'great.' You'll also notice something weird happened just there with the typos. Let's make another variable—let’s say Rust = 'great'—and then print out Rust$. This again prints out 'great.' However, if we go back and print Ruby again, you will see that it has overwritten the previous Ruby variable with 'also great.'
00:16:43.030
The reason for this oddity is that AppleSoft BASIC allows you to have variable names up to 256 bytes long. Such long variable names are allowed, but it only looks at the first two characters of the variable name. Everything beyond that is discarded, so you have to be very careful when constructing variables. I’m glad programming languages have improved since BASIC.
00:17:03.250
So, I believe Ruby would be a much better programming language to use on the Apple II, don’t you? I think it’s a little bit easier to work with than what we’ve just seen. The question that I had was: how is it possible to squeeze Ruby onto the Apple II? This project is what I have been working on. You might first think: well, Ruby is written in C, so why don’t we just compile CRuby to the 6502? There are indeed compilers out there that will do that, and then we can just run Ruby. Fantastic! Job over, we’re done.
00:17:53.730
Nope! It's not going to work that way. CRuby expects certain things, like a file system, which this doesn't have. There were operating systems available for the Apple II that had file systems, but keep in mind that these machines did not actually come with a hard disk; you could buy them, but they were very, very expensive. So we would have to remove all that code from CRuby as it expects a Unicode system. Obviously, this doesn’t support Unicode because that didn't exist when this computer was created, and CRuby has a minimum expectation for it to have ASCII, of course, because this utilizes a subset of ASCII.
00:18:56.960
You get your letters and most punctuation, but if you want curly brackets, no go! The other thing is that the binary for CRuby is over three megabytes in size. While we could do crazy things like loading multiple floppy disks or creating a special language card with banked memory, it just won’t work; CRuby is simply too big and too clunky for an old machine like the Apple II. However, there's MRuby, which is Ruby designed for embedding in small systems. Awesome, we’ll put MRuby on the Apple II; that should be much easier!
00:19:39.390
Unfortunately, MRuby is still too big. There’s a great project called M Ruby/C that allows you to squeeze MRuby onto some surprisingly small systems, but it still requires a lot of ROM space to store your code, VM, and all that stuff. We really want the entire runtime to fit in RAM. MRuby is still going to be too big. The other thing is that C is too high-level of a programming language to program on something like this. I know it sounds crazy to call C a high-level programming language, but when you write a switch statement in C, it can actually be implemented by the compiler in one of many different ways.
00:20:37.820
The code that you write doesn’t directly translate into assembly, and with such a restricted machine, we want to have tight control over exactly what code is written. This is why we probably are going to choose assembly over C. So is all hope lost? I've been working on it for a little over a year now, and we’re still in the early alpha stages, but I would like to present to you: nRuby for the Apple II!
00:21:20.790
nRuby brings you all the joys of Ruby, but it's really small, really, really small! It’s not going to have feature parity with Ruby, but it still has a lot of the features that make Ruby a great programming language. You can have a total of 256 objects. Sure, that’s fewer objects than you get when running an empty Ruby program, because of all the stuff Ruby does initially, but still, 256 objects lets you do dozens of different things. It’s written entirely in assembly. We’ll talk about that a little bit, and that’s why it’s taken a while and is still very much in the alpha stage.
00:22:05.610
Now, why is it called nRuby? I thought of calling it nano Ruby, but nRuby is smaller than that, so we just chop off half of an M and get nRuby. Now, I know what questions some of you are going to ask, so let’s address this upfront: will it run Rails? No. Why? Why would you ask that? Now, once we get to maybe the beta stage or version 1.0, if someone wants to implement a nano version of Rails to run within this, that would be amazing, but I’m not sure if it's even possible to do with all the restrictions in place.
00:22:51.410
So enough talking! Show us a demo! Let me load this up—I got on here on this floppy disk, one of only two floppy disks in the world with the alpha version of nRuby. Let’s see how this goes! I’m going to go ahead and eject the slides, turning off the computer. I’ve got to wait a little bit for the RAM to clear out. As you may remember, with older computers, the RAM takes a little while to go to zero. If I turn on the computer too fast, a bunch of gibberish pops up on the screen.
00:23:25.990
All righty, it’s loading and reading for the disk. But I'm going to go ahead and catalog to make sure we have the right disk. There we have the nRuby executable! So we are going to go ahead and do a binary run of nRuby. It takes a second to load, and we’re now put into an IRB-style prompt.
00:24:07.330
So, let’s do what you have to do the first time in any programming language: puts 'Hello Ruby', and it prints out 'Hello, Ruby!' Well, while this may seem like a relatively simple program, there is a parser entirely written in assembly that processes every single character and figures out that we have a local variable called ‘Ruby’ and a literal string. The parser then creates VM code that can run inside the nRuby virtual machine.
00:24:54.520
The virtual machine then goes ahead and reads through the bytecode. It creates an object using a surprisingly small but still complicated memory management tool which manages the memory inside of nRuby. It then calls the puts command, looks it up locally, and prints it out to the string. There’s an amazing amount of stuff that contributes to printing 'Hello, Ruby.' Let’s try and do something a little bit more interesting. We’ll use the same example we did before.
00:25:41.230
So we’ll type in Ruby = 'great', and then Rust = 'also great'. You’ll note here that when I do the assignment, it’s not printing out the string afterwards because assignments are currently being evaluated as statements, not as expressions. For those of you who know what that means, that’s something I’m working on: making sure everything is treated as an expression, so you can nest assignments and things like that.
00:28:18.610
Let’s go back to the slides. I’m sorry everyone, this is the problem with alpha software. However, if you’re interested in this topic, I’m going to be posting updates about nRuby on my Twitter handle. If you’re waiting to use nRuby in production, as I just accidentally demonstrated, it's clearly not quite production ready. But let’s go ahead and return to the slides so we can discuss the internals—at least the parts that work.
00:29:09.210
Alright, let's skip ahead. The last thing I want to discuss, for those of you who are technically inclined, is a little peek beneath the hood of nRuby and how it’s actually implemented. One of the questions I get: assembly? My god, that's the worst form of programming! It must be incredibly difficult and unbearable—why would anyone want to do that? I’m here to tell you that assembly is not as bad as many people make it seem.
00:29:41.450
There are several reasons for this. First, assembly does very little. It’s very easy to learn assembly programming because there aren’t that many commands on a processor like the 6502. The challenge lies in taking all those little components and effectively composing them into useful programs. Actually sitting down and learning assembly can be straightforward, given that you only have 56 instructions in total, many of which are just variations of each other.
00:30:29.719
Second, in order to do this project, I actually applied test-driven development in assembly, and that's somewhat surprising for many people. I’m not sure why this gets applause! It is mind-blowing to me that most people don’t do test-driven development in assembly, because it simplifies the entire process. Essentially, you call out your subroutine in assembly, load a value into the A register, compare that value with what you expected, and print one outcome if it’s correct or print an error to the screen if it isn’t.
00:31:24.090
You would utilize that to run the program, and it outputs various characters to the screen. You then refer to the source code to determine which tests passed and which ones failed. This is significantly less code than in something like RSpec. Assembly code is readable once you know what’s going on—it’s divided into three columns: an optional label, a mnemonic (which is a shortened form of the instruction's name), and arguments for those instructions that require them.
00:32:08.840
The previous program we wrote to print 'Hello Ruby' can appear in assembly, which looks far more readable than the machine code we were previously using. Again, we’re loading the Y register with a literal value, that is, a number with a hashtag, like 12. We have a label called 'loop' that just says rather than determining what address this line of code corresponds to, we can simply reference that label later.
00:32:44.200
We then load the A register with a value from another variable called text, execute the code, and have our final branch statement pointing to the label from earlier.
00:33:29.370
This makes the code a significantly smoother programming experience once you’re familiar with the details. So what does modern Apple II development look like? I’m not using my vintage machine for development work; instead, I’m using an emulator called the Virtual 2 emulator. It’s fun because it simulates those clicky noises and offers a reasonably good debugger that lets you visualize everything happening inside RAM.
00:34:00.254
Using a debugger is very important; it allows you to navigate your program step by step. When it goes out of control, it really goes out of control! As for the assembler I'm using to convert the text files I wrote in assembly into actual bytecode, it’s called Merlin 32. I really like Merlin 32 because it's simple to use, and it's also a descendant of previous assemblers created for the Apple II.
00:34:38.200
Another option is the CC65 assembler, which is specifically for compiling C into the 6502 processor. While CC65 is an assembler, I found it much more difficult to use, which is why I opted to work with Merlin. I’m also utilizing a tool called Apple Commander to convert binaries into disk images. Notably, this utility is written in Java and can take binaries and put them into virtual disks. I then use a program called ADT Pro to transfer the disk image from my computer to the Apple II.
00:35:34.220
Clearly, there’s no USB port on the Apple II, so I require a USB to serial port adapter, which ADT recognizes. However, the Apple II also lacks an RS-232 serial port, so I need a serial port to null modem cable. I hope I don’t lose this because there’s only one person I found who makes these anymore. Finally, how does nRuby manage memory? That’s an interesting topic since one of Ruby’s main functions is helping programmers manage memory effectively.
00:36:42.290
The 6502 memory is divided into pages of 256 bytes. Internally, I take 16 of those pages and assign them as the space where we will store our heap. Each of those 256-byte pages is divided into 16-byte-long slots. Each one of those slots can contain a single byte, giving it a unique value. This allows me to reference objects using just one byte, indicating where the object or part of an object is stored in memory.
00:37:33.680
Initially, I implemented this by numbering the bytes in an ordered fashion. So, page zero started with slot 0, slot 1, etc. I needed to write a program that could take these slot addresses and convert them to the actual addresses that I would reference in memory. For instance, if we have a slot ID of CF based on my layout, that would translate to page 0C and byte F0. These hexadecimal values must be converted from CF to F0 and 0C.
00:38:25.380
Something very helpful happened: C is the first nibble of that byte, and the page takes that first nibble while F simply shifts over. Therefore, we can utilize arithmetic shifts for our calculations to go from slot ID to memory address, streamlining the process. Consequently, we can make the new assembly code even more concise, replacing all prior shifting operations with logical operations measuring against F0 and zero, which clearly speeds things up.
00:38:56.590
If you're still lost, don’t worry! The process confused me, too, while I was writing it. Fortunately, the beauty of test-driven development is that I had tests with correct values and expectations, allowing me to iterate on the code until I finally got it to work.
00:39:34.890
So what’s next for nRuby? Obviously, I need to finish nRuby and reach that mythical version 1.0 state, moving past the current alpha stage. I also need to release it on GitHub. This has yet to occur because some of the build tools I’m using, while technically freely available, aren’t open-source. As a result, I need to remove anything that might seem to inhabit a licensing gray area.
00:40:06.160
I would also love to get nRuby functioning on other processors like the MSP430, which I find interesting because it’s a modern processor that you can still obtain today. It’s much simpler to work with than the 6502 and notably has subtraction—oh, how I wish I had subtraction in 6502 code!
00:40:27.990
Another fantastic feature of the MSP430 processors is that they can operate at very low power, meaning if we can transition nRuby to an MSP430 platform, we could potentially use a lemon, a zinc plate, and a copper plate wired up a processor and execute Ruby programs powered by a lemon indefinitely. This would certainly challenge the notion that Ruby is inefficient and power-hungry.
00:40:40.240
Additionally, there are other computers that utilize the 6502 for which I would love to create nRuby. This includes the Nintendo Entertainment System, which right now is my holy grail for nRuby. It would be amazing to write games in Ruby on the NES, even though it only has 2 kilobytes of RAM, but 40 kilobytes of ROM space, which means there is a slim possibility of developing something together. However, I might have to cut down on the number of objects available.
00:40:57.660
So what’s next for you? If you’re intrigued by the topics discussed here or enjoyed this talk, I have some recommendations for further learning. There’s a talk titled ‘Reverse engineering the MOS 6502,’ which is a fantastic technical deep dive where they strip the chip and analyze where each transistor is, explaining precisely how the 6502 processor works—it's a wonderfully detailed video! Fortunately, you can find it on YouTube to pause and follow along.
00:41:23.520
There’s another talk by the same person called ‘The Ultimate Commodore 64 Talk,’ which gives a similarly intricate examination of how the Commodore 64 works—another very interesting computer using a 6502. Also, be sure to check out the demoscene, where enthusiasts write programs for these older computers and strive to exploit their capabilities, creating impressive 3D graphics on machines like the Commodore 64.
00:41:54.270
If you enjoy the fun of assembly programming and the challenge of solving puzzles but want to skip the pitfalls of actual vintage assembly programming, there are some great games you can explore. For example, there’s a game called TIS-100 from a company called Zachtronics, where you write assembly code to solve various problems. Another game is Human Resource Machine, where you drag and drop to program effectively in assembly in a humorous setting involving humans and a corporate department.
00:42:46.210
If you would like to try retro programming for yourself, I'd suggest working with the Apple II or Commodore 64, though the Apple II is slightly simpler, making it recommended for beginners. The Commodore 64, while more powerful, has a broader range of coding resources available if you wish to write in this environment. If you go to the Apple Help page and search for historical documents, you can actually find some useful resources from Apple's own documentation that relate directly to the Apple II—pretty cool!
00:43:33.820
If you’re inclined to program retro games rather than computer programs, I’d recommend starting with the Atari 2600. It's a very limited platform making it easier to write games—while the more advanced consoles using newer models (like the Super Nintendo) can yield more complex graphics, they can also be quite a hassle to program.
00:44:05.720
So start with older or simpler undertakings if you prefer the fun of coding and want to dip a toe into vintage programming. My talk seems to have frozen up, so that’s fantastic! Luckily, the next slide is to express my thanks to all of you for attending.
00:44:26.040
I have shared my Twitter handle, GitHub link, and email address for anyone wanting to discuss this further with me. Don't hesitate to come up to me afterwards or ask questions, or reach out through Twitter or email—I'm always happy to discuss these subjects and share the passion for programming. Thank you very much for your time!