-
Notifications
You must be signed in to change notification settings - Fork 14
Hardware Design Notes
Several other people had the idea to combine an AVR with a Z80 before me, but none of these projects provided all the capabilities that I wanted, and some of them had not shared any code. I have taken inspiration from these projects but I designed this specific circuit and software implementation myself. The design choices I've made are explained below.
The ATmega1284p doesn't have enough I/O to interface with all of the Z80's bus lines, so I used an SPI I/O expander to provide two additional 8-bit I/O ports. Because of the SPI interface, the signals on the I/O expander are an order of magnitude slower than those directly on the AVR. I have tried to optimize performance by connecting the most critical signals directly to the AVR, and the rest to the I/O expander. The AVR has direct connections for the LSB of the address bus, the data bus, and the control lines which need to be toggled quickly to handle I/O requests and DMA transfers: IORQ, MREQ, RD, WR, BUSREQ, and BUSACK.
The MSB of the address bus and the remaining control lines--INT, NMI, RESET, M1, HALT, and RFSH--are on the I/O expander. Since the upper 8 bits of the address bus only have to change once every 256 bytes, having them on the I/O expander is not a major limiting factor for DMA transfer speed. Having INT and NMI on the expander is not ideal because it imposes a minimum latency and a maximum frequency on interrupt requests, but tradeoffs had to be made. In REV1 and REV2 of the board, MREQ, BUSREQ, and BUSACK were on the I/O expander, and M1 and HALT were connected directly to the AVR. I found that having BUSREQ on the I/O expander caused very slow I/O performance because BUSREQ is now used to hand back bus control to the Z80, so I reshuffled some of the pins on REV3.
PB0 on the AVR is used as an output to flash an LED by the bootloader, so it cannot safely be connected to an output signal on the Z80. Therefore, I used it for the BUSREQ signal, which is always an input on both the Z80 and flip flop, so it can be safely used as an output during the boot process. Aside from the constraints above, pin assignments were chosen to ease routing of traces between the chips and the pins on the RC2014 bus.
The AVR produces a 10MHz clock signal for the Z80 using hardware PWM. The PWM clock can be divided if desired to prevent time-sensitive software from running too quickly. When debugging, the clock is brought under software control so that the AVR can sample the bus for each clock cycle. Under software control, the clock can achieve a maximum frequency of about 4MHz but when tracing information is being logged, the attainable clock rate slows down to several hundred KHz.
Because the AVR cannot respond to an I/O request quickly enough to satisfy the timing demands of the Z80, the IORQ line also sets a flip-flop that automatically asserts the WAIT line on the Z80 to add wait states until the AVR can respond. The AVR polls the IORQ line in a tight loop and services the request when IORQ goes low. When the AVR has finished servicing the I/O request, it brings the reset line on the flip-flop low to deassert the WAIT line on the Z80.
However, care must be taken to avoid bus contention when switching the bus directions. Therefore, after setting the data bus port to output in response to an IO read, the AVR asserts the BUSRQ line before releasing the WAIT line. The Z80 samples this before the next machine cycle begins and tristates the data bus to avoid any bus contention. The AVR releases the BUSREQ signal after it has switched its data port back to an input and it's safe for the Z80 to resume control of the bus. I got this idea from the AVR/Z80 project by just4fun. Because the BUSREQ line must be asserted anyway while transferring the bus back to the Z80, in REV3 I used it to the reset the flip-flop, freeing up an additional pin on the AVR that was previously used in REV1 and REV2 solely to reset the flip flop.
In my initial design, I was bringing the clock under software control to perform cycle-exact timing when handing the bus back after an I/O request. However, I discovered that this interfered with other peripherals on the bus that relied on having a stable clock frequency, such as a YM2149 sound card.
The HALT input on the AVR is connected to the Z80 through a diode so that it can also be grounded using a push button without shorting the halt pin on the Z80. Thus, it is possible to return to the AVR-based monitor system automatically when the Z80 executes a halt instruction, or manually by pressing the halt button. The HALT pin has the internal pullup resistor enabled so that the halt signal is high (inactive) when neither the HALT signal is asserted by the Z80 nor the button is pressed. When the HALT signal is detected, the AVR stops the Z80's clock and returns control to its own command-line interface. To prevent constant checking of the HALT line on the I/O expander from adding extra latency to I/O request loop, I only check it once every 64K iterations of the run loop.
The SPI signals are exported to the user pins on the RC2014 bus. This should allow additional SPI-based peripherals to be added to the RC2014 bus and the AVR can act as a bridge between them and the Z80. The RC2014 bus lines USER1-6 are connected to SCK, MISO, MOSI, IO expander chip select, and 2 unassigned chip select lines, respectively. The other half of the 74HCT139 is used as an SPI chip select multiplexer to allow the AVR to control up to 4 SPI peripherals with just 2 I/O pins. The traces to these pads can be cut or the pins can be omitted if this is not desired. The MCP23S17 I/O expander has a 3-bit serial addressing scheme so it should be possible to add up to 7 additional I/O expanders to the bus with only a single chip select line shared between all 8 I/O expanders. The I/O expander on the z80ctrl board is hardcoded to address 0.