Somehow the couple of days just before Christmas turned out to be very productive for my Project:65 homebrew computer. Very frustrating, too – but we’ll get to that in due time. Today, I’m going to focus on the design of the recent additions, and next week we can get into the implementation and inevitable debugging. Trust me, it’ll be worth it.
So far, the Project:65 computer has consisted of two parts: a 65c02 processor and an 8 kB EEPROM. I’ve gotten those two chips talking to each other and seen the system run stably at up to 4 MHz. At that point, the only way to monitor what the system was doing was by watching activity on the address bus, and the complexity of programs was limited by the fact that the only memory locations I could modify were the registers in the 6502 itself.
I decided to tackle two birds with one stone by adding RAM and IO to the system simultaneously. That also meant tackling the address decoding logic, which controls which chip is active at any given time – the RAM, the IO, or the EEPROM. So far, I’d been faking it by keeping the EEPROM active at all times.
For this stage of the project, the plan is to use one 65c22 VIA for input and output. The VIA has its pros and cons, as I mentioned last week, but its one major benefit is that it’s part of the 65xx family, and so it’s designed to play nice with the 6502. Hooking it up to the circuit is straightforward. It connects to the data bus and the bottom 4 bits of the address bus. It connects directly to the system clock and reset lines, and has an input for the 6502’s RW (read/write) output. Basically, the 6502 and 6522 speak the same “language” as far as understanding how clock timing and read/write signals are handled.
The ’22 also has a pair of chip enable lines – one that’s active high, and another that’s active low. I suppose the idea is that you can use whichever is most convenient for your circuit and hardwire the other. In my case, I hooked the active high line to +5v. I’ll talk about the active-low chip enable later.
For memory, I’m using an inexpensive 32kB static RAM from Cypress, the CY7C199. Static RAMs are the popular choice for these sorts of hobbyist projects because they’re easier to work with than dynamic RAMs, which require periodic refreshes. Back in the day, economics necessitated the move to DRAM for mass-market machines like the Apple II and Commodore 64, but that’s not a major concern for me here in 2012.
The bigger concern for me is that the CY7C199 doesn’t share exactly the same interface as the 65xx chips. Some massaging of signals has to happen to get these chips working together. Instead of the combined RW line, the SRAM has explicit Output Enable and Write Enable pins. Now it may seem like one of those is just the inverse of the other, but there’s another complication – timing. When the 6502 wants to write something to a memory location, there’s a complex interaction of the address bus, the write signal, and the system clock. I owe a lot to Garth Wilson’s pages for spelling this out to me, but I’ll try to summarize here.
When the 6502 writes data, the RW signal can go low (meaning “write”) while the system clock is low. But the address to be written to isn’t necessarily valid until the clock goes high. So we need to be careful to only write to the RAM chip when three things are true:
- The address on the address bus refers to this chip (that whole address decoder thing I mentioned before).
- The RW signal from the 6502 is low.
- The system clock is high.
The 6522 has the RW and system clock as inputs, and handles most of that internally. Here, we’re on our own.
And that, finally, brings us to the topic of address decoding. The basic idea is simple: the CPU can only talk to one other chip at a time, so the decoder has to make sure that only one other chip is enabled at any moment. The way we do that is by looking at the upper bits of the address bus and deciding, based on our memory map, which chip should be enabled and which chips should be disabled.
Memory map? That’s where we decide where the individual chips are going to live in the 6502’s 16-bit address space. It’s not that difficult, really, because my choices are fairly limited:
- EEPROM: When the CPU resets, it looks in memory locations $FFFC and $FFFD to figure out where to go. Unless we want to get into some kind of fancy RAM-loading-at-startup-time thing, the easiest way to do that is to have the EEPROM sitting at the top of memory, from $E000 to $FFFF.
- SRAM: The CPU is really built around the idea that it’ll have RAM available in what’s known as Zero Page ($0000 to $00FF). The CPU’s stack is also hard coded to exist in the second page of memory, from $0100 to $01FF. You can fake that a bit with your memory logic – for example, on the Atari 2600 Zero Page and the stack both reference the same physical memory, but… let’s not do that. Our 32 kB SRAM can live between $0000 and $7FFF.
- VIA: By process of elimination, the 6522 VIA gets mapped somewhere in the middle. In my first attempt at the memory decoder logic, I put it at $C000. It only has 16 bytes of registers, but those will get accidentally repeated throughout the entire block from $C000 to $DFFF. Later on, we could subdivide this space and add some more IO components.
How does that map help us figure out which chip to enable? Well, if the top three bits of the address – A15, A14, and A13 – are all ones, then you’re in the EEPROM’s address space. If A15 and A14 are 1 and A13 is 0, that’s the VIA’s memory block. And any time A15 is 0, the CPU is trying to talk to the SRAM.
Incidentally, if A15 is 1 and A14 is 0, you’re in no-man’s land – the empty spot from $8000 to $BFFF. I don’t care what happens then, because at that point any code that’s running has already gone completely off the rails.
To help visualize this when I was debugging the system, I made a set of diagrams in a free program called Logisim. I probably should have done that first, since I made some pretty major mistakes with my first design. If you know what NAND and NOR gates are, and if you’ve read this post carefully, you can probably pick out those problems pretty easily.
That’s enough design talk for now. Next time we’ll get into implementation, debugging… and a couple iterations of redesigning.