A few weeks ago, I put my WDC65c02 microprocessor through its paces with a free run, where it ran a constant stream of no-op instructions hardwired onto its data bus. Since then, I’ve spent my time on an Arduino-based programmer for a 2865 EEPROM. Once I got that working, it was time to see if I could get the two pieces – processor and ROM – to work together, and run a program.
That leads to two questions. First, what kind of program should it run? Second, how will I tell if it’s actually working? After all, I don’t have any input or output capabilities yet. Also, I don’t have any RAM. That limited my options.
With the free run, I was able to get a little bit of information by watching the 6502’s output lines with an Arduino. That really only worked well when the Arduino was also supplying the clock signal. A few days after writing that blog post, a package with some oscillator components arrived. When I hooked the 6502 up to a 1 MHz source, I wasn’t able to read the address lines fast enough to be really confident about what was going on. The circuit might have been working okay, but looking at the data I had a suspicion that the chip might have been occasionally resetting itself. I just couldn’t tell for sure.
Putting all that together, I needed a program that:
- Was as simple as possible.
- Only used ROM and the 65c02’s internal registers – no RAM.
- Would modify the address lines in a way that clearly indicated it was working.
- Would clearly indicate if the CPU reset or jumped to an interrupt handler.
I also had to think about where my 8K ROM chip was going to show up in memory. At this stage, that’s still easy. By hooking up the ROM’s 13 address lines to the CPU, and leaving the 3 highest 6502 address lines disconnected, the ROM would be mirrored in each 8 KB block of address space. To make it a little clearer to myself, I decided to think of the ROM as occupying the last 8 KB block, from $E000 to $FFFF.
Why that block? For the same reason that most 6502 computers have their system ROMs there. The interrupt vectors sit at the very end of memory, and I was going to need those. When the 6502 first resets, it looks in memory locations $FFFC and $FFFD for the location of a piece of code to start running. So I knew I needed to have an address there pointing at my real program. I also knew that any time the CPU reset itself, it would read those two bytes. If I could detect that, I’d know when and if the CPU was resetting.
As for the rest of my program, I just needed something simple – say, something that would toggle an address line on and off at a regular speed. I could then hook that address line up to an Arduino or just an LED to see what was happening. I decided to write a program with two loops in two different sections of memory. The program would run one loop, then jump to the other, and so on.
Effectively, my code would run in 3 different blocks of memory, as shown in the diagram. On reset, it would look at $FFFC, and then jump to the loop at $E000. Then it would jump to $E800 for another loop, and then back to $E000. An LED hooked up to the 6502’s A11 address line would blink on and off as the CPU jumped from one block to another. By counting the number of instructions and clock cycles required, I could time the loop and know how fast the LED was supposed to blink.
Meanwhile, address line A12 would be 0 (low) when the program was running, and only go high if the CPU tried to access memory at or above $F000. If that line went high, I would know the CPU reset itself.
Here’s the code I put together in CbmPrgStudio:
NoLoadAddr *=$E000 ; time for the xy part of the loop is: ; ((5 * 255) + 7) * 255 + 5 = 326915 clocks = ~ .32 seconds at 1 MHz ; so if our outer loop is 6, that should be about a 2 second loop. start1 lda #$6 loop0 ldy #$FF loop1 ldx #$FF loop2 dex bne loop2 dey bne loop1 sec sbc #1 bne loop0 jmp start2 *=$E800 start2 lda #$6 loop3 ldy #$FF loop4 ldx #$FF loop5 dex bne loop5 dey bne loop4 sec sbc #1 bne loop3 jmp start1 *=$FFFC byte $00, $E0 ; reset vector
For the actual circuit, I started with my Free Run circuit diagram. From there, it was mostly a matter of matching up the address and data lines of the CPU and the ROM. Since the ROM is the only chip on the 6502’s bus, there’s no need for address decoding logic yet, so I just wired the 2865’s Chip Enable and Output Enable directly to ground (“enabled”), and the Write Enable to +5v (“disabled”).
One other addition I made to the circuit was a crystal oscillator hooked up to the 6502’s clock input. I decided to start with a 1 MHz oscillator, and see if it worked.
With the oscillator in place, and power supplied by my AdaFruit breadboard power supply, it was time to power on the circuit and watch as… absolutely nothing sensible happened.
Well, really, who would’ve expected a project like this to work correctly the first time? Debugging a minimal system like this, though, proved to be interesting. My LED on A11 was dimly on, instead of blinking, and connecting it to other address lines seemed to show activity of some kind, but nothing that made sense to me.
After puzzling over this for a while, I figured out that I could set things up to single-step through the program and watch what happened – at least to a limited degree. I disconnected the crystal oscillator and hooked the Arduino back up to the 6502’s clock input and to the lowest 8 address lines, and wrote a quick sketch to print out the value on the address bus as the clock changed.
Using this, I was able to see the 6502 go through its reset procedure, read the reset vector at $FFFC and $FFFD, and then jump to $E000. Things looked good as it proceeded through the first few instructions, until it got to the first branch statement: “bne loop2”. From there, it should have taken the branch and gone back to the label “loop2” – but it didn’t. It seemed like it was only moving backward one byte instead of three. (I’ll spare you a long digression on how relative addressing works with the 6502’s program counter, and just say that this was the Wrong Thing To Do).
After branching to the wrong location, the code wandered off into Never-Never-Land – never to make sense again. I was worried that the CPU wasn’t reading from the ROM correctly, or something. Then again, every time I reset the CPU it went off the rails at exactly the same place and in exactly the same way. So at least it was consistent.
I started to recheck my wiring, to see if I’d gotten a couple lines backward either in this circuit or in the EEPROM programmer, but everything checked out. I went over my source code line by line to make sure I hadn’t screwed up. I checked the memory accesses I was reading against CbmPrgStudio’s assembly dump of my code to make sure I knew exactly which instruction was going wrong. The problem was staring me in the face for quite a while before I noticed it.
In CbmPrgStudio’s disassembly, both “bne loop2” and “bne loop1” have the same machine code value, “D0 FF”, even though they should be jumping by different amounts to get to their respective labels. Even more obviously wrong, the “jmp start2” at the end of the loop was being assembled to “4C FF FF” – where the “FF FF” ought to be “00 E8”, the address of “start2” in little-endian form. The assembler was screwing up all my addresses – it was a software problem the whole time!
The problem, by the way, was that I originally had colons after all my labels, as you can see in the picture. I may have carried that over from the ARM assembly I’ve been coding on the Raspberry Pi, but the latest version of CbmPrgStudio apparently didn’t like that syntax. Now that’s fair enough, but it also didn’t produce any errors or warning messages about it. Not cool, man.
I removed the extraneous colons and reprogrammed my EEPROM, and when I powered it up again my Arduino monitor showed the program slowly making its way through the loops. Then I took the Arduino out and put my 1 MHz oscillator back in place, and was rewarded with the slow, steady blink of the LED hooked up to A11. I let it run for a couple hours, with the Arduino monitoring A12 to see if it would reset at all, but it remained steady throughout.
Feeling a little bolder, I replaced the oscillator with a 4 MHz part, and I continued to see good results. That was incredibly satisfying. This system is finally starting to resemble a real computer. Somehow, though, I wonder if this hasn’t been the easy part.