A common feature of 1980s computers is that they booted into a useful state. This was one of the main things that separated them from the hobbyist and trainer boards that preceded them. You could plunk an Apple ][ or a TRS-80 on your desk, flip the switch, and it would boot up into a state that allowed you to make it do something. This sounds ridiculously obvious now, but it was a big deal at the time. It was when home computers crossed the threshold from toy to tool.
Veronica needs to cross that threshold now. Her creator is already kind of a tool, but so far Veronica is still a toy. What we need is a ROM monitor. A very basic piece of software that will allow you to do a little something on the machine besides stare at a blinking light. In my case, the significance is even greater. I’ve said from the beginning that I wanted to build a computer from scratch, and that my definition of “computer” was “a machine that you can use to write code for itself, on itself”. At the moment, Veronica is more like an embedded device or a game console. She can only be programmed by tethering another computer to her. Let’s see what we can do about that.
Before we dive into this, I should mention my assembly macros. I’m developing with a ca65-based pipeline, and one of the awesome features is a powerful macro language. That goes a very long way to making assembly easy to write and understand. Listed below is my typical set of macros. They should all be self-explanatory except for a couple.
The first slightly weird one is CALL16. That’s a shortcut for calling functions with a 16-bit argument. I have some standard 6502 zero-page areas specified, and one of those holds arguments for function calls. Implementing a high-level generalized stack-based function calling convention on the 6502 is a pretty complex task, and not something I need for this little bit of ROM code. Instead, I simply pass function parameters in pre-designated places on the zero-page. The code is faster, smaller, and simpler to read this way. It’s more limited, because you don’t have a proper local context for each function call. You need to be aware that you’re stepping on the previous context when calling a new function, so nesting calls must be done carefully. We’re programming without a net at this low level.
The other slightly odd macros are the GPU commands (static and variable versions). These are convenience routines for passing the two-byte commands through the memory-mapped GPU command register at memory location $EFFF. This is a command byte followed by a parameter byte. Each two-byte command is queued up and processed on the GPU when it has time. This is all documented on the various GPU pages of Veronica’s blog entries.
Okay, with the housekeeping out of the way, let’s get down to business. I already have some input routines that I wrote for my keyboard interface, so we’ll be leveraging that. After checking all the RAM, Veronica will boot up into a loop that takes a command line of input:
Once we have the command line as a null-terminated string in a buffer, we parse the command and match it up to known commands. This is done using a jump-table, which is a very powerful assembly technique to which the 6502 is well-suited.
Now to write the commands themselves. The main job of a ROM monitor is to examine, modify, and execute memory. These three things are the minimum requirement to program a computer. My “Read” command takes a starting address and a number of bytes, then displays that memory onscreen in a human-readable format.
Here’s a video of booting up into ROM, and using the Read command to look at some memory. At startup, RAM tends to be filled with $42, because that’s a special value used by the RAM diagnostic.
Now we need to be able to modify memory. That’s done with the Write command. I won’t go over all the code for all the commands here, because they’re quite similar. The Write command takes a starting address, then an arbitrary list of space-delimited hex bytes. Here’s a demonstration of examining memory at $4000, then writing some NOPs into that same area. After each modification, the Write command automatically displays the memory you modified so you can see the change.
The command to execute code (“Go”) is the last piece of the puzzle, and it’s trivial. Here’s a demonstration that brings it all together. I fire up Veronica’s ROM monitor, enter some code into memory (hand assembled) to display an ASCII character set, then run it. With this act, I have proven that Veronica meets my definition of “computer”. You can use her to write code for herself, entirely untethered to any other devices.
Oh, just more thing. Since this ROM monitor isn’t very exciting with just those housekeeping commands, and I have those gamepads ready to go, I added this:
Every ROM monitor should have a “Pong” command, if you ask me.
Look mom- I made a computer!