A Minimalist First Step Into Complex 6502 System Design.
I’ve had a serious interest in computer hardware since high school. In between attempts to design my own CPU, I also took some interest in the 6502 shortly after finding Veronica. I promptly ordered two R65C02s- and did nothing with them. Keep in mind this was shortly before university, where I took actual computer architecture and logic design classes. Building a computer around an established chip just didn’t seem all that interesting anymore. Not when I had the skills to make something better.
Well, I don’t see why I can’t do something with these languishing chips. However I don’t want to do the same, boring Apple II clone every 6502 project seems to be. Instead, I’d like to take this as an opportunity to push the 6502 to it’s limit. Show that it doesn’t have to be limited to endless reproductions of ’80’s computers. Cram as many modern features as possible. Make it do things you never thought a 50+ year old chip could do.
But, before jumping into the second most complicated electronics project I’ve ever attempted, it would be wise to get experience with a smaller project. After all, I know little of the practical problems of a 6502 system. To understand that, I need a working 6502 system to experiment with. You see the issue.
My goal here is to build a simpler prototype of my expected end goal. In particular, I want to be able to modify and upgrade the prototype with the functionality of the final design. That includes support for a complex multi-master external bus, expanded memory, multitasking, and much more.
But hey, none of that will happen if I can’t do the basics first. And oh wow are the basics hard when it comes to computers.
BIG UPDATE:
I have decommissioned the minimal development board. After gaining some practical experience, I found it far too limited to be useful. Instead, I moved over to my new single board development system which is about as complicated to build yet far more valuable for 6502 system design.
I do not recommend using the minimal design, and present it only as a historical curiosity.
6502 System Design
My goal for this stretch is to get the critical core features done. That means RAM, ROM, and basic I/O. More complex systems can be built on top as things progress.
Much of the complexity of this development board lies in ensuring it can be expanded into a prototype of my final CPU card design. Were it not for that, this system could be done with 6 or so ICs and maybe one weekend’s worth of work.
Memory Map
6502s can address 64KiB of memory. Based on the size of commonly available parts, and future design goals, I cut this into 8KiB blocks.
Locations of the RAM and ROM are constrained by the 6502’s built-in assumptions. You need ROM in FFFA-FFFF for hardware vectors, and RAM at 0000-0020 to use the zero-page and stack. Might as well round both up to 8KiB and call it done.
Settings holds all the hardware settings registers. These are, at the moment, largely nonexistent. For now all this holds is a basic I/O port. Again, easiest to just set aside the entire 8KiB even if I only need two bytes.
Block Number | Address Range | Function |
---|---|---|
7 | 0xE000-0xFFFF | ROM |
6 | 0xC000-0xDFFF | Input/Output |
5 | 0xA000-0xBFFF | N/A |
4 | 0x8000-0x9FFF | N/A |
3 | 0x6000-0x7FFF | N/A |
2 | 0x4000-0x5FFF | N/A |
1 | 0x2000-0x3FFF | N/A |
0 | 0x0000-0x1FFF | RAM |
Hardware-wise these roles are filled by a AS6C6264 RAM, AT28C64B ROM, a UMR245 parallel to USB converter, and a handful of standard 74HC-type logic.
Uncommitted blocks are mostly there for future expansion. They are empty until needed.
CPU and Support Circuitry
Unlike most of it’s contemporaries, the 6502 does not need a lot of external support. It still needs a little bit of help to get up and running.
All 6502s need a clock input. Lucky us, it only needs to be a single 5V square wave at the CPU frequency. It’s even possible to plug a crystal or RC oscillator right into the 6502 with no extra parts required.
I chose to cleanly separate the clock generator from the CPU for flexibility. A 4MHz ceramic resonator is coupled to an ‘HC74 flip-flop wired as a divider. PHI0 gets fed a clean 2MHz signal.
Retrospectively, I feel this is an over-complication. I should have just plugged a 2MHz clock directly into the CPU. Too late to change that; I’ll just have to live with it.
Decoding the bus signals requires two gates. Annoyingly the 6502’s R/W line uses logic high to indicate a bus read while most ICs want logic low on their enable. Part of me suspects this is a clever ploy to get people to buffer a heavily loaded signal.
Bus writes are a little harder. You have to synthesize the write signal using R/W and PHI2. Most parts want a write signal that’s active low and terminates on a rising edge.
Both signals are easy to derive with just two NAND gates:
Lastly, the 6502 needs a reset signal. This should trigger when powered up or when the user presses a button. After briefly considering a dedicated chip, I just threw together the classic RC+Schmitt trigger circuit.
Reset circuits this simple tend to be glitchy and unreliable. I don’t expect any trouble, though I’m keeping my eye on it.
Address Decoding and Memory
Cutting the address space into 8KiB blocks requires a little special handling. A15-A13 are fed into an ‘HC137 3:8 decoder. Each output corresponds to a block.
RAM and ROM chips tend to have a “chip select” input. Usually this is active low, so the ‘HC137 signals can be plugged directly in. Read and write require no special treatment; CS will override them. Just plug them straight in.
The UM245R requires more Intel-type (separate read/write) signals. It also requires two ports; one for data, the other for flags. I simply decoded one bit of the address for each. Inefficient, but simplification is more valuable. OR gates handle the distribution of signals.
Note the extra NAND gate inverter. For reasons beyond me, the UM245R has a write signal opposite of what most devices expect. Careful reading of the datasheet shows I can’t realistically get around this any other way. I found this out the hard way, yes.
Due to a quirk with the 6502 instruction set, assigning flags to D7 and D6 is preferred. Otherwise it’s arbitrary; the 6502 compare instructions are reasonably cheap and flexible.
Full Schematic
Might as well show the entire schematic, as of completing these sections:
I had to tweak the circuit quite a bit as it matured, so there’s probably a few errors in this schematic. I’m not going to make a PCB with it, so I don’t really feel like triple checking it. It will almost certainly become obsolete as I modify the circuit.
Circuit Construction
Going straight to a PCB is a bad idea; this is supposed to work out the subtle kinks before that point. Instead I bought the largest protoboard I could afford:
While it looks big, once you start laying down parts it feels pretty cramped. Putting the RAM, ROM, and CPU in a line would have worked out a lot better. I don’t know if there’s a truly “optimal” solution.
Once you put some stuff on it, it sure looks small doesn’t it?
If you’re ever desperate for something boring to do, zoom in and try to find all the connections I forgot to solder.
There’s so much going on, I put the pinouts of the ICs on one page for reference. This was VERY helpful.
You forget how thick wires are until you stack them sixteen deep. There’s enough leftover space for a small amount of extras.
Since there’s a lot of wires to keep track of, I made a color code:
- Red: +5V
- Black: GND
- Green: Address signals
- White: Data signals
- Yellow: Clocked signals
- Blue: Misc.
This gets kind of ambiguous. I did what I could, but the address decoder is kind of a random set of colors as a result. If I had the ten color wire kit I’d add a color for the intermediate address decoder sections. Maybe one for the CPU control signals too. It would also help when I add on extras that need their own wire groups. Alas, I bought the six color kit.
I put some LEDs on to indicate various hardware states. Unlike the wires, I didn’t really think about a color code.
- Power (Red)
- USB interrupt enable (Green)
I feel like yellow or blue would work better for power, since red/green is a well-trod indicator for complimentary states. I only have four LED colors, so I’ll probably have to put some more thought into it.
Testing the Development Board
There are well over a hundred connections on this board (I can’t be bothered to count them all, but back of the envelope calculations make 150-200 a reasonable estimate). For the most part screwing one up will cause subtle errors that can go unnoticed for a very long time. While a full test would require complex software, a much simpler test can show if any serious bugs exist.
Simply print “Hello world!” to the USB port. Endlessly, with no other responsibilities. At the very least, this will show if the address decoding is working. It won’t detect many errors. But if it can be made to work, then a more complex program can be made on top.
So how well did the first test go?
Okay, not what I was expecting, but not unsurprising. A little tweaking revealed the screen wasn’t really blank, but instead receiving lots of blank characters. Hmm.
What followed was a long session of hardware debugging. Among the things I found and fixed:
- More than a few unsoldered connections.
- More than a few wiring errors.
- I tried to “cheat” with the UM245R by not inverting the write signal, then had to add in the inverter anyways.
- The interrupt control flip-flop was wired backwards.
- A few short circuits between adjacent lines.
- My test program was surprisingly buggy for how simple it is.
After fixing the most egregious bugs, I got this:
This is more in line with what I expected initially. I’m still getting tons of blank characters. Multiple characters are getting printed, which indicates some trouble with the write line.
A little probing revealed a short in the newly added inverter. Clearing that up gave me this:
Despite the text coming through more or less correctly, I’m still getting a ton of blank characters. Perplexing.
I ended up using my logic analyzer to watch the UM245R directly. I could literally see each letter get written, and each instruction getting fetched. Using this information I could patch out a few bugs in my test program. However, nothing I did, hardware or software fixed anything. All the evidence I have points to the UM245R.
You know, the UM245R can be powered directly off of USB, so what happens when I power it up on it’s own?
…right. With literally nothing connected, I still get all this junk. But it’s not that simple. It only did this sometimes. Yay, unreliable intermittent bugs are the best type!
I hardwired it to transmit a single character when I press a button. This worked. So it’s not the UM245R per se, but possibly the socket? Or wires? Probing them didn’t show any problems, but sometimes the extra load of a probe can hide problems. Using an external power supply makes the circuit behave very differently from powering it off USB; another tantalizing bit of not-quite-conclusive evidence around the UM245R.
I think I have to stop things there. This is rapidly hitting the “tedious bullshit no one wants to read about” point. Technically I have proven the basic circuit works as expected. Finding the exact source of this bug might take days. Much of that time will be spent running the same basic tests over and over, with only minor variations. If you really want to experience that, just re-read this testing section until you finally lose what’s left of your sanity.
UPDATE: After a bit of poking about with the address decoding I managed to get this:
θemmo!wosme!! to you too.
It would appear the problem is around the ‘HC125. I know that because when I press it, stuff happens.
The corrupted text in this case is because I managed to break a pin off the UM245R. If you compare the text to an ASCII chart, you’ll notice they all have bit 0 set- the pin I managed to break. This neatly explains why half the characters aren’t correct- this is indeed “Hello world!” with bit 0 set to 1.
Except theta. No idea how or why that showed up.
UPDATE THE SECOND: While developing this project further, I fixed up the last(?) of the errors. I captured the “Hello world!” in action as part of other development, so here’s how it should look “live”:
Finishing Up
Normally I wouldn’t show partial results like this. However, not every project is a straight line from start to success. Might as well show my own struggles once in a while.
Despite not getting the result I wanted, there’s still a lot of success here. I can confirm the 6502 is fetching and executing instructions properly. While trying to debug my test program, I also (accidentally) proved the RAM is working. Even though the USB port isn’t working correctly I could still get my message though some of the time. Given that I have made stuff that just flat out refused to work despite my best efforts, I’m well ahead of where I could be.
6502s are old enough that they have debugging support built in. If you poke the various signals in just the right way, you can step the CPU on a per-instruction or per-cycle basis. Very useful for debugging with limited tools. Worth investigating, if nothing else.
My logic analyzer is a phenomenally useful tool for computer projects. Without it, I would not be able to confirm if the 6502 was really working. Unfortunately it was made in 1992, which is only slightly younger than me- it has aged much less gracefully than I have. I’m going to need to fix it up a bit to make it worth using. Perhaps I’ll write about it.
I’ll need to expand the board later on. I have enough space to put a couple of minor additions, but I need to think about breaking out certain signals for the more interesting stuff. My original plan isn’t going to work with all those wires in the way.
When I started this project, I didn’t intend it to be a complex dive into hardware debugging. I mean, I knew I’d have to do that eventually. I just didn’t expect it from day one. This bug is proving exceptionally hard to nail down, and I need to clear my head out with something less stressful.
I have several follow-up projects ready to go, as soon as I can fix my I/O. That will have to wait until next time. Whenever that comes.
Have a question? Comment? Insight? Post below!