Cross-developing on homebrew 6502s and other single board computer projects.
Over on Veronica, things have progressed to the point of needing some fairly substantial amounts of 6502 code to put in ROM. Writing this stuff by hand and manually assembling it is getting old very quick, and I need something more efficient. What I’d like is a system where I can just write assembly code on my laptop, push a button, and have that code execute on the homebrew computer. This is a pretty standard model of development; throughout history, development for a particular computer has been done on a bigger (better) computer connected to it. In modern times, an easy analogy is console game development (where I worked for many years). Consoles like the Xbox and PlayStation are very good at running games, but they’re lousy as general purpose computers. You wouldn’t want to do development for those machines on those machines directly.
Once I had this system running for Veronica, I realized there’s nothing especially Veronica-specific about it. Any homebrew single-board-computer-type project that needs ROM code could use it. Hence, this article!
First and foremost, this system depends on an AVR-based EEPROM programming setup. If, like me, you don’t want to buy a fancy expensive EEPROM programmer, head over to this project and build your own. An AVR microcontroller can be hooked up to an EEPROM as external RAM, and presto you have a programmer for a couple of bucks. I’m partial to the ATTiny series for this purpose. So, the trick here is that your 6502 (or other CPU) code needs to be included in the package that is downloaded to the AVR, so that the AVR can then write it into the EEPROM on your homebrew computer.
To start with, you need code to drive the AVR to program the EEPROM. Here’s my code for that:
This code will program EEPROMs of this type, using this circuit. Basically all it’s doing is using shift registers to push bytes into the EEPROM from the ATTiny. EEPROMs of this type are written to just like SRAMs, but the timing needs to be done just right in order to trigger “page write” mode. Otherwise writing is painfully slow. This article has full details on the page write mode, if you’d like to know more.
If you look at the top of the source code above, you’ll see that it #includes “rom.c”. That’s where the data is that will end up in the SBC’s ROM. By including the 6502 code directly in the AVR’s source code, it ends up in Program Memory on the AVR. That’s necessary because AVRs have very little RAM, but quite a bit of program memory. Your AVR needs to have enough program memory to hold the entire contents of the SBC’s ROM.
Here’s what that “rom.c” file looks like:
As you can see, it’s just a big C array with some hex bytes in it. Those hex bytes land at the very end of ROM, which is where the 6502 looks for the special vectors used to start up. But what’s this? Yet another file is being included- romImage.inc. Where does THAT file come from? Well, this extra layer of indirection allows the ROM source code to change, but keeps the special reset vectors and such in the correct place at the end of ROM.
Here’s a sample romImage.inc file:
That’s just a piece of it. The whole thing is 4090 hex bytes separated by commas. My ROM is exactly 4k in size. So, you can see how splicing that hex data into the array in rom.c would create a 4k ROM image as a global C array, with the final 6 bytes being the special 6502 vectors. Neat!
So where does romImage.inc come from? Now we get to the meat of things. This is all done automagically by our old friend ‘make’. Here’s the makefile for this project:
This makefile relies on some external tools, but any standard un*x will have them. It also relies on the awesome 6502 toolset called CC65. The assembler in that package is called CA65, and that’s only element being used here. If you’re on OSX, there’s no compiled binary of CC65 available. However, you can build it from source very easily with these instructions.
So here’s what ‘make’ is doing:
Finds the 6502 assembly source file (called romImage.S)
Assembles that file using CA65. The flags are set such that CA65 generates raw machine code- no relocation headers or ELF format or anything fancy.
The ‘dd‘ tool is used to pad that machine code file to exactly 4090 bytes (for a 4k ROM). This is critical, because otherwise the special vectors won’t land in the right place at the top of the ROM image. Our source code can be any size (less than 4090 bytes). This step will simply pad the final ROM image with zeroes to make everything work out.
The padded binary image is run through the ‘hexdump‘ tool to make a human-readable C-source version
The C-source is written out to romImage.inc, which is already included by the AVR code
The AVR code is compiled, installed, and run on the AVR using avrdude in the usual way. The rom image has been magically swept up in this process, hiding in the AVR’s program memory
When the AVR automatically reboots after the code download, it copies the code into the EEPROM and then just halts, with the satisfaction of a job well done.
It sounds like a lot of fiddly steps, and it is, but once it’s set up, you can write familiar 6502 source code like this:
… push one button, and in a couple of seconds you have new ROM running on your homebrew computer. You don’t necessarily need to understand that whole ‘make’ process to use this technique. Just change the numbers for your ROM size and starting address, and it should just work in any un*x environment.
One final note- once you start using a system like this to write large amounts of code, you may quickly find that debugging by binary reduction is no longer sufficient. For most low-level SBC code, you can build things up very slowly and get away with no debugging tools beyond careful examination of your code. When things get a little tougher, a simulator is a big help. The cross-development system I describe here dovetails nicely with this generic 6502 simulator. The hexdump stage of this build process can be tweaked to output code that can be pasted directly into memory on that simulator. From there, you can step through your code and debug it quite effectively. Here’s the hexdump command I use for this:
hexdump -ve ’10/1 “%.2x ” 1/0 “\n”‘ romImage.bin
Rapid iteration is key to efficient software development, so I hope some of you find this technique useful when you come to write the ROM for whatever crazy retro computer project you’re working on!