If you’re building a computer from scratch, where do you start? I suppose you could hook up all the pieces, apply power, and… spend the next few weeks trying to figure out what isn’t working. Yeah, that seems like a bad way to do it.
When you write software, it’s best to do it in small pieces that can be tested individually. This is even true if the pieces don’t do anything useful, because it really is possible to write code that’ll do nothing useful and still do it incorrectly. If you’re being formal about your software development, you’ll even write the tests first and then fill them in with code.
For my Project:65 computer, I wanted to find the most stripped-down piece that I could hook up and get to do… well, anything, really. If you start with just the 6502 CPU, how much hardware do you have to add to make it do something, just to prove that the chip is alive? And what’s the smallest, simplest way to give it what it needs?
You usually think of a microprocessor as needing RAM, ROM, and some kind of IO in order to work. But it’s easy to write an assembly language routine that runs from ROM and doesn’t store any values in RAM. So RAM is optional.
What about IO? I don’t need a specific result; I just need to prove that the CPU is doing what’s expected of it. One way to do that is to monitor the flow control – the changing value in the CPU’s program counter that shows where in memory it’s reading instructions from. When the 6502 processor wants to read the next instruction from ROM, it puts the value in its program counter on its 16 address bus pins. If you watch the voltages on those pins, you can see what the program counter’s value is, and no other IO is needed.
How about ROM, though? We still need to get our program from somewhere, right? Well, that’s where things get really interesting. To understand what I’m doing next, you need two facts:
- What does the 6502 do when it starts up? How does it know what code to run? The chip expects that the last few bytes of its address space will contain a set of vectors – basically, the addresses where it should start reading instructions from in the event of an interrupt or reset. That’s why the system ROM is usually at the end of the memory map on 6502-family systems like the Commodore 64. When the CPU gets a reset signal, it reads the data in locations $FFFC and $FFFD and sets the program counter. From there, it’s off and running.
- The 6502 (like every other CPU) has a NOP instruction – “no operation”. When the CPU is instructed to NOP, it just increments the program counter and moves on to the next instruction. The opcode for the 6502’s NOP instruction is the single byte $EA.
With those two facts, it’s possible to set up a 6502 to do a “free run” – an endless cycle through its address space. All you have to do is wire up the 6502’s 8-bit data bus to always read the value $EA. That’s 11101010 in binary – you just hook up +5v to the “1” pins, and ground the “0” pins. No matter what memory location the 6502 tries to read, it’ll always see $EA on its data bus. When it resets, it’ll read $EAEA from the reset vector, and set the program counter accordingly. Then it tries to read from address $EAEA and receives a NOP instruction. So it increments the program counter and reads another NOP from location $EAEB, and so on until it reads a NOP from $FFFF and circles back around to the beginning of the address space. And so on. Forever!
Okay, there are a few minor details. The 6502 needs a clock, for example. That’s just an input that switches from 0v to +5v. Usually you’d hook up a crystal oscillator, but since the part I ordered hasn’t arrived yet, I just hooked it up to one pin of my Arduino. It’s not a very fast clock, or a very regular clock, but right now it doesn’t have to be.
There are a few other pins on the 6502 that have to have particular values before it will do anything. For example, there are two interrupt input pins – IRQ and NMI – that should be kept high at all times. When I was initially debugging this setup, I hooked up IRQ correctly, but forgot about NMI and left it floating. This resulted in a circuit that seemed to work correctly, but if I happened to touch the breadboard at all the CPU would get confused and reset. That’s a very spooky error to try to debug, until you understand what’s happening.
I used a program called Fritzing to create this circuit diagram. It’s my first try with that program, and kind of crude, but it gets the general point across. In addition to generating the clock, I connected the upper 8 address bits of the 6502 to the Arduino’s digital inputs, so the Arduino could report the value back to my PC. When the circuit worked correctly, I could see that value change from $EA to $EB, $EC… $FF, $00, $01, etc., incrementing once for every 256 NOP instructions.
The software on the Arduino side is trivial. It runs a loop where it toggles the clock output on and off, and then reads the values of the 8 address lines I hooked up. If the value has changed, it writes out the hexadecimal value on its serial port. I didn’t have enough digital inputs to read all 16 address lines, but this was enough to verify that the 6502 was working correctly.
At this point, I have a CPU that I know works, but that can’t do anything besides spin in circles. And it’s not even spinning in circles quickly – I only got it up to a couple kilohertz with this method. The next step for Project:65 has to be getting it to read a real program, instead of an endless stream of NOPs. I hope to have more on that topic next week!