Now for something a little different. I was first exposed to computers back in the late 1970s and early 1980s. Suffice it to say, placing my hands on the keyboard of an Apple //+ was a watershed moment which pretty much set the course of my life from that point on. The heart of the Apple // was the 6502 microprocessor. I learned to program on that chip, along with millions of other people. It was the chip that brought computers and video games to hundreds of millions of homes and schools, and I think it’s safe to say that it sparked a revolution. The world was ready for personal computers, but all the contemporary CPU offerings (notably from Intel, AMD, and TI) were very expensive. The 6502 offered all the power of the others, for 1/10th of the price. You could find 6502s in the entire Apple // line (except the GS), the Commodore 64, the Vic-20, the Atari computers (except the ST), the BBC Micro & Acorn, the Atari 2600, the Nintendo Entertainment System, and many others. If you used a personal computer or played a videogame in the 1970s, 1980s, or early 1990s, there’s a very good chance it had a 6502 in it. It was arguably the first RISC chip, and the first to do pipelining. It has a clean, elegant instruction set and gets much more done with a clock cycle than anything else of the era.
I finally decided that I wanted to pick up a naked 6502 of my very own, and see if I can get it to do something. Unlike my other projects, I don’t have a destination in mind here. This is the hacking equivalent of walking to the bus station with my credit card and seeing what happens. I’m calling this project Veronica, and I’m going to let her tell me what she wants to be. So let’s get rolling and see where we end up!
One more thing- I am by no means an expert on CPUs or how to build with them. I’m learning as I go here, so feel free to speak up in the comments below if you see something I’m doing wrong!
So…chip acquired. Now what?
Well, the first thing to do with a CPU is see if we can bring it up and get it to execute some instructions. At their core (pardon the pun), CPUs are really very simple beasts. They do basic arithmetic, they branch and jump to different lines of code, and they move bits to and from memory. That’s it. Everything else is just layers of tricks to make them faster.
The first thing a CPU needs to run is a clock. I picked up a surplus crystal oscillator to handle this. The data sheet for it is really sketchy, so I put it on the breadboard by itself and played around with the pins to make sure I knew how to hook it up properly.
I’m starting with 1Mhz, just to be safe, and it seems to me that being able to single-step the execution would be useful. Here’s what I came up with to do all of that:
On the left half of the schematic, there’s a 555 timer configured to generate a single-shot 2ms pulse on the rising edge of a button press. This is roughly equivalent to 500kHz, which is comfortably above the recommended minimum clock speed of the 6502 (which I believe is 100Khz?). On the right is a standard 1Mhz crystal oscillator. The toggle switch in the middle chooses between them, and also acts as a run/stop switch for the CPU. When in “single-step” mode, the CPU is paused, and clocks are read from the manual button press. When in “run” mode, the 555 timer is ignored, and the crystal’s pulse train is fed directly to the CPU.
Notice that I’m switching the power sources of both clocks, not the clock signals themselves. It’s important to keep the clock signal as physically close to the CPU is possible, and I want to run potentially long wires to a switch and back. With this arrangement, both clocks can sit right next to the CPU, but the switch can be located anywhere that is convenient. I also have a 3.6Mhz oscillator that I may try putting in there, to see how Veronica performs on that.
Note: Since building this, I’ve learned that the clock input needs to be held high while the CPU is stopped to prevent internal data loss. I’ll be reworking my single-stepping clock circuit to achieve this. For this simple test, though, it works fine as shown.
Once we have a clock, the CPU will need to execute some code. According to the datasheet, When the CPU powers on, it goes through a reset sequence, then fetches an address from memory location $FFFC (called the Restart Vector). This location is the very top of memory, and the chip is internally hardwired to fetch an address from there after a reset. This address is put into the program counter, and the CPU starts fetching and executing instructions from there. Note than I’m using ‘$’ to indicate hexadecimal numbers, which was the custom at the time (before the C language took over the world, with its ’0x’ prefix).
But wait- we don’t have any memory! Well, we can get by without any for now. The CPU is putting address values out, and expecting to get values back in on the data lines. We can just hard-wire those data lines with some fake bytes, and the CPU won’t know the difference. A good place to start is $EA, which is the 6502 op-code for NOP (no operation). A NOP does nothing except cause the CPU to advance to the next line of code. So if we hardwire the data lines to $EA, what will happen? On reset, the CPU will think it’s fetching a two byte address from $FFFC, and will get $EAEA back. It will then start executing code from memory location $EAEA. Every fetch cycle, the CPU will ask for the next instruction, and will get $EA (NOP). It will execute that, and continue until it gets to the end of “memory”, at which point it resets and does it all again. This process is called a Free Run.
Here’s what the circuit looks like to do that:
On the left is a reset circuit, which consists of a push button, and an RC network. The RC network holds the reset line active for about 50ms, to make sure the reset “takes”.
Note that, per the datasheet, if we were using the NMOS version, we would need pull-up resistors on the IRQ, NMI, and RDY lines. The CMOS version (65C02) sets these pins to high-impedence for us, so we can let them float. Any of those three signal inputs, if not tied off properly, could set off cause chains of events in the CPU that we don’t want to deal with right now. Refer to the datasheet if you’d like to know what all these signals do.
On the right, you can see that the Data lines are all hard coded to +5V or ground, to force 1s and 0s, respectively. As explained above, that bit pattern (11101010) is $EA. No matter what data the CPU requests from the RAM (which isn’t really there), it will get $EA back. Sneaky! Lastly, you can see I have a 16-bit bus connecting the address lines to my HexOut module. This is just so I can see what addresses the CPU is looking for in memory. That tells us a lot about what the CPU is doing.
We don’t need resistors on the Data lines because, per the datasheet, they have tri-state buffers built in.
The Eagle symbol for the 65C02 that you see in the schematic is one I made myself. I couldn’t get any of the 6502 Eagle libraries available online to work, so I made this one. If you’d like a copy, here’s the current version of it. It uses a 40-pin DIP package (also included in the library).
So, now we have a clock circuit, and a CPU set up to free run. Here’s what it all looks like on the breadboard:
The main breadboard holds the 6502, the fast clock, and the reset circuit. The smaller board has the toggle for choosing clocks, and the 555 circuit for single-stepping the CPU. You’ll also see JuiceBridge making an appearance here, and of course HexOut is coming in very handy.
That’s fine and dandy, but this thread is useless without video, am I right? Pictures don’t do it justice, so let’s watch the process unfold. In the first video, you see me turn on the power, at which point the CPU is in a junk state. I’m holding reset for one clock cycle, then advancing the clock step by step. You can see the CPU go through its reset process, which results in various junk appearing on the address lines. After seven clocks (per the datasheet), I hesitate so you can see the CPU start looking at bytes $FFFC and $FFFD in memory. That’s when it’s ready to start executing code, and needs to know where to find some. Of course, it gets $EAEA, so you see it jump to that address, and attempt to execute. From then on, it’s getting NOPs, and on every second clock you see it fetch from the next address. The in-between clocks are when it is executing the NOP that it fetched. It repeats fetching and executing NOP after NOP after NOP until it runs off the end of the 64k of addressable memory (which isn’t actually there).
In this second video, I unleash our girl at 1MHz. Obviously, the addresses are now going by too fast to see much, but I can hold Reset to halt it and see where it is in memory at that moment (after which, it resets).
What’s next for Veronica? Well, she needs some real memory, I suppose…