For my host design I used what I knew: the 2716 EPROM and 6116 static RAM. The nice thing about this pair of chips is that they are pin for pin compatible: you can swap them in a design.
We can take advantage of this in the emulator to replace the 2716 on the host board with a 6116 in the emulator. That way we can put code into the 6116 (since it's a RAM) and the host treats it like an EPROM.
If we just plugged the 6116 into the host there would be no way to get code into it. So we need to have it accessible from a controller. That would allow us read binary files from an SD card and write them to the 6116, then let the host use it in place of the 2716 it thinks is there.
The question is how to make that happen.
Basically we want to dual port the 6116: let two different systems use it. The controller would write into it and the host would read from it.
History note: the Apple ][ design dual ported the system RAM to allow both the CPU and the video system to have interleaved access (the video hardware had access while the CPU was in it's "working internally" phase of each cycle. This allowed for very simple, efficient display hardware, with the bonus that video access took care of the dynamic RAM's refresh requirements (see Digital Circuits 5: Memories for more on dynamic RAM).
Below is the schematic of the emulator. At the bottom-left and bottom right is the address and data related signals, respectively, from the host.
The data bus from the host (on the right) is connected to the 6116 through a tristate buffer (the '244) that isolates the RAM from the host while the emulator is loading data into the RAM. The output enable of the RAM is controlled by the same signal that controls that '244. That's what the NAND gates at the bottom are for. The output of the RAM and the buffer to connect the RAM's data pins to the host are controlled by the output enable from the host EPROM connection, gated by the emulator's mode signal (low when the 6116 is being loaded, high when the host has access). Using NANDs and inverting the host's output enable means that data flows from the 6116 to the host when the host asks for it (the /OE from the host is low) and the emulator is in host access mode.
There is another '244 connecting the controller to the data bus of the 6116 while the emulator is in program mode. More about that later.
Now consider the address bus from the host (bottom left). It doesn't connect to the 6116 directly or though a buffer. Instead it goes connects to the B sides of 11 dual input multiplexers (the 74LS157s in the center of the schematic). 2K of memory needs an 11-bit binary number to cover all locations. The chip enable signal from the host goes through a multiplexer as well. The outputs of the multiplexers connect to the corresponding pins of the 6116.
The other inputs to the multiplexers? They come from a bank of 4-bit binary counters (on the left in the schematic) that are chained together to make a single 12-bit counter. Since we only need 11 address lines, the highest valued output of the counter is not used. The A side of the final multiplexer connects to a chip select control signal described later.
The outputs of the multiplexers go to the corresponding input of the 6116.
So that explains the dual-porting, the host connection, and the emulator address generation. Let's turn our attention to the top part of the schematic. Core to this is a MCP23017 that is controlled via I2C by the control board.
Port A is simple: it's just the 8-bit data to be placed into the 6116. When the emulator is in programming mode the second '244 is enabled, connecting the output of port A to the 6116's data pins.
Port B is a collection of single-bit control signals, starting with bit 0:
- /PROGRAM is low when the emulator is in program mode: i.e. the 6116 address is provided by the counters, and it's data by port A of the 23017; the host is cut off from the 6116 in this mode. When /PROGRAM is high, the 6116 is connected to the host.
- /WRITE connects to the write enable input of the 6116. When it goes low the values on its data pins are written into the location specified by the values on its address pins.
- /SELECT connects to the 6116's chip enable pin when in program mode. This serves to enable the chip for subsequent reading writing. The 6116 does nothing unless its chip enable pin is low. When in EMULATE mode, the chip enable comes from the host.
- ADDR_CLK increments the address counter when it is pulsed low briefly.
- ADDR_RST is normally low, and making it high briefly resets the address counter to 0.
- INDICATOR is connected to an LED simply as an indication of when the emulator is in EMULATE mode. I used a separate port B output for this due to the current draw of an LED. Just to be safe, and since I had port B outputs to spare.
Loading data into the 6116 is simple, but the sequencing has to be right:
- Place the emulator into PROGRAM mode (take /PROGRAM low)
- Reset the address counter (pulse ADDR_RST high)
- For each byte to be programmed:
- Output the next byte on port A of the 23017
- Activate the 6116 (take /SELECT low)
- Write the data (pulse /WRITE low)
- Deactivate the 6116 (take /SELECT high)
- Advance the address counter (pulse ADDR_CLK low)
- Place the emulator into EMULATE mode (take /PROGRAM high)
One limitation incurred by using an address counter (rather than an additional 23017 to provide the address) is that arbitrary locations cannot be written. That's not a problem since the use case is to load the 6116 with the contents of a 2K EPROM; writing arbitrary locations isn't required.
One advantage of using an address counter is that it provides a better example of the circuits we've been looking at in previous parts of this guide series.
This circuit has a little of everything: logic gates, counters, multiplexers, and memory. It also includes the 74244 chip that we saw back in part 2. Let's have a closer look this time.