A Memory Map for the TLC-MBC
Right now TLC-MBC is a vegetable. There is no way for communication between her and the world and even if there was she wouldn’t know what to do with it! She needs memory and, like Johnny 5, she craves input, but these cannot be given haphazardly. So I have to create a memory map for her so that she knows where her memory and peripherals are. The 65C02 has certain requirements:
- Interrupt vectors (RST, IRQ and NMI) occupy the last six bytes of memory, so this is usually ROM of some kind.
- The 65C02 needs a place for temporary storage, called the Stack, this is from $0100 to $01FF so this has to be ram.
Another complication is that any Input/Output must occupy part of the memory map. This is different to processors like for example the Z80. On a Z80 you can have a full 64K of ram and ROM as the I/O has it’s own memory space. You might have ram at $A000 and an I/O chip at $A000 but because of it’s hardware and software instructions the Z80 knows when it is talking to memory or I/O and there is no conflict. The 65C02 can have only one “thing” at each memory location, so it’s either memory or I/O. Because the 65C02 has 16 address lines (a0-A15) it has an address space of 64K that can be divided up between ram, ROM and I/O.
Currently, there are two sizes of EEPROM available that would be usable here, namely the 2864 (8K) and 28256 (32K). If at some point I want to add a high-level language, that requires a lot of space. For example EHBasic is around 11K. I do not want to be messing about burning a ROM image across multiple EEPROMs. This leaves the 28256, it should occupy the upper half, $8000-$FFFF, of the memory map. Certainly plenty of space for whatever I want to put there.
A similar story to ROM, the 623308/6264 (8K) and the CY62256 (32K). I also have some 2016/6116 (2K) and 4116 DRAM 16K x 1). I could use the 8K but then if I want to expand the memory later to 32K that means another 3 28 pin sockets. Same goes for the 6116 and DRAM so again I’m left with a 32K chip!! And it will occupy the lower half of the memory map.
Traditionally I/O chips do not take up much space at all usually 2 or 4 BYTES, more complicated chips can take up a whole 16 bytes… So even if I wanted 16 of them that would be only 256 bytes at most, but where should they live?
Clearly decisions have to be made. I cannot have 32K of ram and 32k of ROM, there’s no room left in the memory map for I/O. There are a couple of ways to solve this problem:
1> “Hide” a portion of memory and replace it with I/O. As far as I’m concerned this will only work for me if it’s at the extreme start or end of memory, whether it’s ram or ROM. But there are limiting factors to this approach. It cannot be at the start of ram as page zero is valuable memory and page one is the Stack. It also cannot be at the end of ROM as that is where the RESET, IRQ and NMI vectors are. This leaves the end of ram or the start of ROM as good candidates.
2> I could use the top address line on the ROM as a bank switch. This would split the ROM into 2 16K blocks addressable in the same memory space, $C000-$FFFF. One bank could have Basic and be the home computer mode, while the other half could have the assembler and other dev tools and be the development system mode. The line could be switched either under software by a pin from a VIA or even a physical toggle switch. With the I/O block starting at $8000, that would still leave room to put another 8K ROM at $A000-$BFFF.
Approach 2 sounds very appealing to me, but could get messy. What if I’m using bank 0 and want to use something from bank 1, I can’t really, without restarting the system. It looks like approach 1 is really the more attractive of the two, but how to implement it? Well, what are the requirements?
Ideally I would like a block of 16 bytes for each device, this would use A0-A3 leaving A15-A4 to be used to create these memory blocks. A4-A6 could be used to decode 8 of these blocks (total 128byte block) meaning that A15-A7 would have to have the value of $800 (1000 0000 0 in binary). That’s a 9 input Or gate with an inverter on A15, I don’t think you’ll find one!
Final Memory Map
After a great deal of thought and debating with myself I compromised on the following memory map:
- $0000-$7FFF: 32K RAM
- $8100-$811F: DEV 0
- $8120-$813F: DEV 1
- $8140-$815F: DEV 2
- $8160-$817F: DEV 3
- $8180-$819F: DEV4
- 81A0-$81BF: DEV 5
- 81C0-$81DF: DEV 6
- 81E0-$81FF: DEV 7
- $8400-$FFFF: 31K ROM
Chip-wise, this is the most economical way I could devise. Memory-wise, I’m “losing” 1K of ROM to use (8×32) 256bytes of I/O. The first 74HC138 decodes 8 blocks of 1K each in the top half of the memory map, the first is used for the I/O Block Select (IOBS). Below that is a NAND gate with IOBS and A15. When A15 is high (top half of memory) and IOBS is high (I/O is not active) then ROMCS is active. If A15 is high but IOBS is low (I/O is active) then ROMCS goes high deselecting the ROM. If A15 is low then ROMCS is inactive while RAMCS is active.
Now that the memory map is set, I can rest easy when I add the memory and I/O knowing that there will not be any memory conflicts, one less problem to worry about.