There's a lot of stuff to fit into the 64KB address space of the CPU. Not only is there 144KB of RAM and 8KB of ROM, but all the device registers have to be located in there, too, since the 6502 doesn't have a dedicated I/O area. So I need some kind of banking scheme in order to make it all fit.
In keeping with the goal of simplicity, I'm going to go with an uncomplicated model, and have a banked area in the bottom 32KB of CPU address space, which can be relocated to any part of RAM; the 32KB size allows me to simply use the A15 line on the CPU as an indicator of whether we're in the banked area or not. And, for purposes of flexibility, I'm going to make the area from $0000-01FF (the zero-page/stack area) separately switchable. I also need a common area, which I'll put in the next 16KB of address space; this area is contained in a separate pair of 8KB RAM chips.
It isn't hard to decide where the ROM will go, because the 6502 needs its interrupt vectors located in the top of memory. So we'll put the 8KB ROM in the top 8KB of address space. This leaves 8KB free between the bottom of the ROM and the top of the common RAM. Since I'm using a 3:8 de-multiplexer to decide what goes where in this half of memory, this 8KB gets divided into two 4KB chunks. We still need somewhere to put the I/O devices, so we'll claim the top 4KB chunk for the I/O area. The bottom chunk is, at present, unclaimed, as I don't really have anything to put there. I'm planning on running the appropriate lines out to a connector of some sort, in order to make this area into a simple expansion port.
Now, how to control the banked area? The common approach is to just have a simple switch mechanism, where the RAM is divided up into chunks the size of the banked area, and have the banked area access one of these discrete chunks at a time. Doing this in the System/65 design would divide the 128KB of main RAM into four 32KB chunks. This is rather less than optimal.
Given the need for more flexibility, and the fact that one of my eventual goals is to run some sort of simple multi-tasking OS on the System/65, I'd like to be able to switch the banked area on a finer granularity than 32KB. So what I'm going with for the banking scheme is an Intel-style segment selector, instead. This is a little more complex, but a whole lot more flexible. How it works is that the banking control register is shifted left to the highest bits of the RAM address, and then added to the address obtained from the CPU to form the actual RAM address. This allows the banked area to serve as a "sliding" window into the RAM, starting from anywhere in RAM.
We'll limit the banking control register to eight bits, since that way there's no possibility of a partially-written register screwing things up. With an eight-bit selector in our seventeen-bit RAM address, we get a granularity of nine bits, or 512 bytes. Our zero-page/stack selector will also be eight bits; with a granularity of 512 bytes and an actual size of 512 bytes, this means that the zero-page/stack selector doesn't even need the adder, and can just act as a simple switch. Conveniently, since the main banked area starts 512 bytes into CPU address space but doesn't subtract 512 from the address being accessed, this means that the main banked area actually starts 512 bytes after the theoretical base address; this means that the same value, when placed in both selectors, yields a perfect contiguous 32KB of RAM!
This scheme comes with its own little quirk, which is that for sufficiently high values of the banking control register, the top of the banked area can go over the top of the RAM size, and wrap around to the bottom of RAM. This starts with register value 193/$C1. So unless there is a compelling reason, it's advisable to set the banking control register only to values from 0 to 160/$C0.
The I/O area is not very complicated. It uses a 4:16 de-multiplexer to divide the 4KB area up into sixteen 256-byte segments, each of which addresses one device. The System/65 doesn't actually have sixteen devices, but the unused spaces could be used if I decide that there's something I'd like installed that wouldn't be too complicated for the project. Of course, the existing devices have nowhere near 256 addresses apiece, but with 4KB of space for such a simple system, we can afford a little waste.
The I/O area is actually counted as part of the expansion port, inasmuch as the expansion port receives any accesses addressed to the I/O area as well. This way, I can fit any device registers on the expansion port into the unused space in the I/O area, and leave the other expansion port space free for, say, an expansion ROM or somesuch.
|0||Banking control registers:
(0 = $0000-01FF area
1 = $0200-7FFF area)
Special thanks is due to Chris Ward of this site for a lot of the basic ideas in this section, as well as opening my eyes to the usefulness of de-multiplexers in address decoding (hey, I did say I'm an electronics newbie.)