.:[ designing a memory bus ]:. kisom 2019-08-20 While the Z80 is an 8-bit processor, it has a 16-bit address space (address pins A0...A15), which means it has a maximum of 64K of accessible memory. This address space has to be split between ROM and RAM (fortunately I/O devices have another access mechanism). The task is to figure out what the split should be and how to accomplish it. In the case of my computer, I'm using a 32KB parallel EEPROM [1] with a parallel SRAM chip [2]. This RAM chip is also 32KB; in order to get the full address space, we need two of them. All three of these pins have a chip enable pin /CE; the leading slash in front of the pin name indicates active-low logic. With active high signalling, a high signal (i.e. a digital 1 or +5V) means active, but with active low signalling, a LOW signal means active. Basically, when /CE is pulled to ground, the chip is enabled. Note that the chips have slightly different names for these pins but they have the same function. For consistency, I'll just use one set of names. It's important to know that the Z80 starts execution at address 0000H, which implies that the ROM should occupy the bottom of the address space. In that case, if we look at the binary representation of addresses, we can make some observations that will govern how we lay out memory: 111111 5432109876543210 +------------------+-------+ | 0010000000000000 | 8192 | | 0100000000000000 | 16384 | | 1000000000000000 | 32768 | +------------------+-------+ If we give 8K of space to the ROM, we get 56K of RAM and an easy way to tell which chip should be selected: address pins A13, A14, and A15 decide which of our three chips should be chosen. The selection table looks like this: +-----+-----+-----+------+ | A13 | A14 | A15 | chip | +-----+-----+-----+------+ | L | L | L | ROM | | x | x | H | RAM1 | | L | x | L | RAM0 | | x | L | L | RAM0 | +-----+-----+-----+------+ Basically, the three conditions are: + the ROM should be chosen if NAND(A13, A14, A15). + RAM1 should be chosen if A15 + RAM2 should be chosen if AND(NOT(A15), OR(A13, A14)). This *could* be done with logic gates, though it might get a little messy on the board. Fortunately, there's a 7400 series chip that does a lot of heavy lifting for us: the 74139 2-of-4 decoder and demux. It has three input pins (/E, A0, and A1) and four output pins (O0, O1, O2, O3). The enable pin is active low while the address pins are active high, so this can be a little confusing, but let's figure this out. Here's the 74139's logic table where H means HIGH and L means LOW (as in signal levels); I've added a final column that expresses the output pins as a binary value with O0 as the LSB. Note that if /E is low, it doesn't matter what A0 and A1 are - the output will be all high values. +----+----+----++----+----+----+----+------+ | /E | A0 | A1 || O0 | O1 | O2 | O3 | O | +----+----+----++----+----+----+----+------+ | H | x | x || H | H | H | H | 1111 | | L | L | L || L | H | H | H | 1110 | | L | H | L || H | L | H | H | 1101 | | L | L | H || H | H | L | H | 1011 | | L | H | H || H | H | H | L | 0111 | +----+----+----++----+----+----+----+------+ There's an important property of this chip: *at most* one output pin is low. If it's not enabled (e.g. /E is high), then none of the output pins are low. This is what we want when using active low logic to select a memory chip. At most one device is active, and if disabled, no devices are active. The Z80 indicates that the address bus has a memory address by pulling its /MREQ pin low; we'll tie that to the /E pin. We can put pins A13 and A14 into an OR gate [3], and connect that to A0. Note that you should connect the unused gate pins to ground [4]. A15 gets connected to A1, which means we can revise our table (condensing the output pins to a single binary value for clarity). +-------+-----+-----+----+-----+------+ | /MREQ | A13 | A14 | A0 | A15 | O | +-------+-----+-----+----+-----+------+ | H | x | x | x | x | 1111 | no memory operation | L | L | L | L | L | 1110 | address is < 8192 | L | H | L | H | L | 1101 | | L | L | H | H | L | 1101 | | L | H | H | H | L | 1101 | | L | x | x | L | H | 1011 | if A15 is high, it doesn't really | L | x | x | H | H | 0111 | matter what A0 is. +-------+-----+-----+----+-----+------+ So far, we know we can tie O0 directly to the ROM's /CE pin, and O1 directly to RAM1's /CE pin. But how do we combine O2 and O3? Let's see what we want, behaviour wise. +----+----+-----+ | O2 | O3 | /CE | +----+----+-----+ | H | H | H | if both pins are high, /CE should be high. | L | H | L | if either O2 or O3 are low, /CE should | H | L | L | be low. +----+----+-----+ This looks almost like an AND gate, which would also pull /CE low if both O2 and O3 are selected. There isn't a case where this happens, so we don't need to consider it. That means we should tie O2 and O3 together behind an AND gate [5], Lets simplify the table above, with A0=OR(A13,A14) and O=(AND(O2,O3),O1,O0). +-------+----+-----+-----+---------------+ | /MREQ | A0 | A15 | O | Memory device | +-------+----+-----+-----+---------------+ | 1 | x | x | 111 | None | | 0 | 0 | 0 | 110 | ROM | | 0 | 1 | 0 | 101 | RAM0 | | 0 | x | 1 | 011 | RAM1 | +-------+----+-----+-----+---------------+ But does this actually work? Let's verify the basic idea in Python. First, let's write a function that returns the memory chip for a given three-bit value: def chip_selected(pins): if pins == 0b111: return None elif pins == 0b110: return "ROM" elif pins == 0b101: return "RAM0" elif pins == 0b011: return "RAM1" raise Exception("multiple devices active ({})".format(bin(pins))) Now, let's emulate the 74139: def demux(e, a0, a1): if e: return (1, 1, 1, 1) if a1: if a0: return (1, 1, 1, 0) return (1, 1, 0, 1) if a0: return (1, 0, 1, 1) return (0, 1, 1, 1) This is really just hardcoding the logic table above. Okay, now the meat of the problem: actually checking our addresses: def memselect(mreq, addr): # ex. memselect(0, 0x8000) # mreq is active high, so it should be 0 or False to enable # memory devices. # the address bus is 16 bits addr = addr & 0xFFFF # get our three chip selection bits from the address. a13 = addr & (1 << 13) a14 = addr & (1 << 14) a15 = addr & (1 << 15) # select an address. a0 = a13 | a14 a1 = a15 (o0, o1, o2, o3) = demux(mreq, a0, a1) ando = o2 & o3 muxval = (ando << 2) | (o1 << 1) | o0 return chip_selected(muxval) Now we should be able to write a self test for this; there's few enough memory addresses that we can test all of them. def self_test(): """test all 16-bit addresses against their expected memory chip.""" for addr in range(0, 8192): assert memselect(0, addr) == "ROM" assert memselect(1, addr) == None print("ROM: OK") for addr in range(8192, 32768): assert memselect(0, addr) == "RAM0" assert memselect(1, addr) == None print("RAM0: OK") for addr in range(32768, 65536): assert memselect(0, addr) == "RAM1" assert memselect(1, addr) == None print("RAM1: OK") The memory chips also have a write enable pin, /WE, and an 'output enable' pin, /OE. These tell the chip whether a read or write is occurring. For the ROM, it might make sense not to wire in the write enable chip. Either way, you can attach the Z80's /WR pin to all three pins /WE and the /RD pin to all three's /OE pin. Throw in some power and you got yourself a memory bus. Total parts list (apart from the Z80), with digikey part numbers: +-----+-----------+------------------+ | Qty | Part | Digikey Part | +-----+-----------+------------------+ | 1 | AT28C256 | AT28C256-15PU-ND | | 2 | CY62256 | 1450-1480-ND | | 1 | SN74HC139 | 296-8230-5-ND | | 1 | SN74HC08N | 296-1570-5-ND | | 1 | SN74HC32N | 296-1589-5-ND | +-----+-----------+------------------+ Finally, there are some things to note: there's an 8K hole that's never used in RAM0's address space. We're also only using a quarter of our ROM space. If we wanted to support multiple 8K ROMs, we could add a DIP-2 switch to address pins A13 and A14 on the EEPROM. Basically: +-----+-----+----------------+-----+ | A13 | A14 | EEPROM address | ROM | +-----+-----+----------------+-----+ | 0 | 0 | 0-1FFFH | 0 | | 1 | 0 | 2000H-3FFFH | 1 | | 0 | 1 | 4000H-5FFFH | 2 | | 1 | 1 | 6000H-7FFFH | 3 | +-----+-----+----------------+-----+ Just a thought. [1] The AT28C256 EEPROM. [2] The CY62256 [3] Otherwise the electric pixies might get a little confusticated and agitate the signals what are coming from the Z80 - you have to give them somewhere to lie down and take a nap. [4] The canonical IC for this is probably the 7432, e.g. the SN74HC32N quad 2-input OR gate. [5] Like the 7408.