I want to add more devices to my computer, memory mapped as usual (see part six). Some of those devices need more bytes than others.
Up until now I’ve been using discrete logic to select the correct device, given the status of the address lines … but as the computer gets more complicated, using simple logic gates becomes more problematic.
- More discrete logic requires more space, more power, more board design, and more development time.
- Each time you add another “layer” to that decoder logic (by “layer” I mean a component whose input depends on the output of another, which in turn may depend on the output of another, and so on) you increase the “propagation time” – the time between address lines changing, and the correct device being selected. Eventually this will limit the max speed the computer will function.
- My computer design is still very-much “in progress”, so I can expect to be revisiting this a lot!
Instead, people are approaching this problem with more programmable logic: GALs, and the like. I’m not familiar with GALs (nor do I have the tools to program them) so that would be something new to learn.
But then I thought … what about an EEPROM? Treat it like a look-up table. The input into the table is the address, and the eight digital outputs could become the select lines for eight different peripherals! All you need to do is write data to the EEPROM that selects just one output pin, for each different address.
The propagation time remains constant, and anytime I want to rearrange my memory map, all I have to do is reflash my EEPROM. It felt like it could work … but had anyone else done this?
Then, at an electronics-and-beer meetup in Nottingham with likeminded geeks, I started explaining my thoughts about my address decoding problem. Before I could get to the bit where I said “has anyone here tried using an EEPROM to do it?” someone interrupted with “have you thought about using an EEPROM to do it?”.
So I had a go. And it worked, first time!
The 74LS00 is used to gate write-enable and output-enable on phi, as in my previous design.
I decided to use an 8KB EEPROM (28c64) for no other reason than I had one or two knocking around which were just too small for me to use for anything else.
There are 16 address lines in my computer (because that’s the 6502 for you) giving me an address space of 64KB. The EEPROM has only 13 address lines, for its 8KB storage space.
So we wire the highest thirteen lines to the EEPROM, leaving the lowest three alone. This means that the EEPROM ignores the lowest three address lines, so one byte in the EEPROM covers eight bytes in the computer.
Effectively, this means that the best “granularity” I can manage (how small a range of bytes I can designate to a device) is eight bytes. This is a bit wasteful as some devices might only need one or two bytes … but it’s still a pretty acceptable compromise. If I ever get to the point where this isn’t enough, I could revise the decoder to use a 16KB EEPROM, which would give me a granularity of four bytes; or a 32KB EEPROM, which would give a granularity of two bytes.
The EEPROM obviously has eight outputs; therefore, I can select one of eight devices.
What if I want more? Well, the great thing is about this circuit is that I could duplicate it for an extra eight devices if/when needed. I just need to remember (when I’m creating the data to go onto the EEPROMs) that only ONE device should be selected at a time.
Creating the data
I’ve written a very quick C program that generates the lookup table to go on the EEPROM. I include it here for reference. It’s not pretty, but it’s commented and should compile on any modern OS. It works by considering each byte of the EEPROM, and scnning through a list of address ranges to decide which device should be selected by it.
It’s worth me mentioning active-low versus active-high selected devices here.
Most devices I’ve encountered seem to be active-low; that is, they consider themselves selected when their select line is pulled low. However: some devices prefer to be pulled high (for example: those Hitachi LCD screens). By dealing with that issue in data in the EEPROM (inverting bits before they get stored) means that we don’t have to add any physical NOT gates in the circuit.
The BBC Micro Memory Map
I can be pretty flexible with my memory map – it’s my computer, after all. But part of me wants to make it vaguely similar to the BBC Micro, to minimise any potential compatibility issues if (ha-ha) I ever get to the point of running BBC programs on my own computer.
So let’s talk about how the Beeb has its devices mapped.
- &0000 – &7FFF – RAM
- &8000 – &BFFF – Sideways ROM
- &C000 – &FFFF – Operating System ROM, apart from:
- &FC00 – &FCFF – FRED
- &FD00 – &FDFF – JIM
- &FE00 – &FEFF – SHEILA
On a Beeb, there’s a 16K ROM for the OS, and another 16K ROM that’s the currently selected “Sideways ROM” (for example: the BASIC interpreter). On my DIY6502, I’m using a single 32K EEPROM doing the job of both 16K ROMs. I intend to implement the OS in the second-half, keeping the first half for a language.
FRED, JIM and SHEILA are areas of memory which do NOT select the OS ROM, and instead select peripherals. In the BBC Micro, FRED and JIM are designated for accessing external expansion devices, and SHEILA is used for the BBC Micro’s internal peripherals.
The DIY6502 Memory Map
It’s not essential for me to adopt the beeb system … but there’s no harm in it either. So to begin with, I’m going to map devices 1 to 7 to the second-half of SHEILA, giving them sixteen bytes each:
- &FE80 – &FE8F – Device 1
- &FE90 – &FE9F – Device 2
- &FEA0 – &FEAF – Device 3
- &FEB0 – &FEBF – Device 4
- &FEC0 – &FECF – Device 5
- &FED0 – &FEDF – Device 6
- &FEE0 – &FEEF – Device 7
I’m not going to implement a 1-MHz bus interface soon, but there’s no harm in considering it in the future. I certainly have some Beeb expansion boards that would be interesting to try. Therefore, I’ll keep the FRED and JIM address ranges reserved for now.
The great thing is about doing this with an EEPROM is that I can shuffle this around later if/when I need to; and thanks to every assembler’s ability to use constants, it’s not an onerous task to modify code when the address mapping changes:
SERIAL_BASE = &FEA0
VIA_BASE = &FEB0
You know the drill, right? Yep: OSHpark, again!
The last six pins of the 40-way backplane connector are now the select lines for the first six devices (from 0 to 5). Device 6 and Device 7 are exposed with a pin-header, so that when I need to use them, I can use a short Dupont wire to join them.
So there we have it – a simple but flexible of building a memory map!