Kinda on a lark, I decided to design an MMU for the 6502. The idea was basically to allow for a protected mode, similar to the x86 (and others). Being a bit of a NES hacker, I'm familiar with basic bank swapping mechanisms, but I decided to go for something more advanced.
The main idea was to extend the address space and implement page tables in general purpose memory. I decided to use a 20-bit address bus and 256-byte pages. This requires 12-bit page addresses in the page table, which (when bumped up to a round number of bytes) leaves four bits for flags. So a page table takes two pages.
So now that I had figured out how I wanted the page tables to function, I had to come up with a way to implement them. I sketched out a few different designs, but I eventually decided that the best way was to make three memory accesses every time the CPU tries to access the memory.
The first access fetches the low byte of the page table entry. The second access fetches the high byte of the page table entry. And the third access reads or writes to the mapped page.
To make this work, the clock must be run at five times the CPU speed (I think). Three external registers are defined:
- PTR - Page Table Register
- PAR - Page Address Register
- PFR - Page Flags Register
The PTR points to the page table. The PAR holds the address of the page about to be accessed and the PFR holds it's flags. Accessing memory works as follows:
Clock Cycle- Connect the PTR, A₁₅₋₈5, and a 0 to the address bus. Read memory.
- Load PAR₀₋₇ from the data bus.
- Connect the PTR, A₈₋₁₅, and a 1 to the address bus. Read memory.
- Load PAR₈₋₁₁ and PFR₀₋₃ from the data bus.
- Connect the PAR and A₀₋₇ to the address bus. Read or write memory.
It may be possible to do cycles 2 and 4 on the falling (or is it rising) clock edge and reduce the required clock speed. (But I don't know for sure.)
I've developed a little schematic of the basic workings of the idea, which you can look at
here. It's somewhat over simplified and is missing a few things, but I think it conveys the gist of it. (In case you're wondering, PXR0 and PXR1 are 74377s. PAR is PXR0₀₋₇ and PXR1₃₋₇. PFR is PXR1₄₋₇.)
Notably missing is the write line handling, which should be ANDed with C₅ and clocking the bus. The bus should be clocked at C₁, C₃, and C₅, but I'm not sure that's safe as the clock cycles would be of inconsistent length.
Also missing is all of the flag handling logic, which ought to be pretty simple. Basically, at cycle 5, if a fault is detected, an interrupt is raised, rather than reading the memory.
Code:
if PFR₀ == 0: fault # Present
if PFR₁ == 0 and RWB == 0: fault # Write
if PFR₂ == 0 and SYNC == 0: fault # Execute
Speaking of interrupts, PTR really ought to be two registers. One for user mode and one for kernel mode. However, I haven't quite worked out the details of that just yet. (Hopefully I can do it without requiring more multiplexers.)
Some parts of the schematic haven't been fully fleshed out yet. In particular, the multiplexers. The only suitable IC that I'm aware of (which isn't saying much) is the 74157 4-bit 2:1 multiplexer, which isn't so bad for the data multiplexer. However, the address multiplexer would require five of them, for a grand total of seven, which is sure to be messy. Any suggestions for a better solution would be greatly appreciated.
Finally, I don't really know how to split the clock in five (or three if that's viable), so there's just a black box. I suppose I could use a decade counter and some AND gates. Although, that wouldn't help with the splitting the clock in three (which seems preferable, if it'd work).
So please take a look and let me know what you think. Suggestions for improvement would be very much appreciated. Also, this is the first complicated electronic thing I've ever designed, so please point out any newbie errors I may have made. Thanks.
Anyway, I'm off to read more datasheets (especially the timing diagrams) to try and see where the inevitable bugs are.