The Spectranet's glue logic is implemented in an Xilinx XC9572 CPLD, which is the modern day ULA. The CPLD will do two major functions:
- trap execution of certain addresses and certain instructions
- provide a paging mechanism for the flash and static RAM
Execution trapper overview
Since the Spectrum has no formal method of using peripheral paged ROMs, it's quite common for peripherals to trap the execution of code at certain addresses to page in its own ROM. The general procedure for trapping the CPU executing at a certain address is:
- Monitor the address bus.
- Look for an active M1 signal from the CPU, along with MREQ, and an address of interest
- Page in our memory and page out Spectrum ROM when this condition is true.
The Z80 CPU makes it easy to find out when it's doing an instruction fetch, since MREQ is active to read a memory address, and M1 is active. An MREQ+M1 cycle only occurs during an instruction fetch. The trapper itself largely consists of simple combinatorial logic that causes the page-in flip flop to latch when an address of interest is decoded.
The second method is to trap a particular instruction, rather than execution of a specific address. This is generally done as follows:
- Monitor the data bus.
- Look for an M1 cycle, and decode the instruction and operands of interest.
- Page in our memory.
Trapping instructions may be more complex, for example, to trap a CALL instruction, the CPLD must decode the CALL instruction on the data bus and also the following two bytes to check that this is a CALL to an address that's of interest to us (for example, our code entry point).
We also have to take into account that another peripheral with an execution trapper and paged ROM might be connected to our through port. Bad things tend to happen when two memories are paged in simultaneously. Unfortunately, the lack of a formal paging mechanism means there's no standard way of dealing with this.
The Spectranet's method is to insist that other peripherals should be on its through port. Fortunately, quite a few peripherals lack a through port (such as the DivIDE), and it's the only place to put it when the Spectranet is being used. Others do have through ports, so the user will need to know that the peripheral will have to go behind the Spectranet if it's not aware of the Spectranet's scheme.
For any other ROM to get paged in, an address in the lowert 16K must be being accessed. Other devices absolutely cannot page their ROMs if an address is being accessed that's >16K, or it would conflict with RAM. The Spectranet solution is to implement a 'daisy chain' priority scheme. Other devices that use this daisy can be either before or after the Spectranet on the edge connector. Devices that don't do this must be on the Spectranet's through port. The daisy chain is implemented by routing the A15 line via the CPLD. Normally, A15 is simply passed through unchanged (modulo an unimportant 10ns propagation delay). However, if the Spectranet's memory is paged in, A15OUT is held high. Any peripheral device will therefore see any memory accesses as if they were upper RAM accesses, and keep their ROM paged out. The Spectranet also only conditionally traps certain addresses, to allow traps to "ripple through" the possible chain of peripherals. For example, on RESET, the Spectranet traps because the RESET signal was seen, and address 0x0000 was trapped. When the Spectranet has completed its reset routine, it pages out restarting to address 0x0000. Now A15OUT is released, any downstream device will pick up the execution address of 0x0000, and can also initialize. When it then restarts to 0x0000 to run the Spectrum ROM's reset routine - since the RESET latch in the Spectranet's CPLD is no longer set, the board won't trap and the Spectrum's ROM will start normally.
The A15 daisy chain can be used like this for an arbitrary number of peripheral devices (up to the point that the delay on the A15 line gets too long, which should be more devices than is practical).
Conditional trapping also allows things like NMI to be trapped without causing a trap on a device which unconditionally traps NMI - if NMI on our board is pressed, when execution at 0x0066 is trapped, since A15OUT is held high, the downstream device won't trap an NMI that wasn't intended for the downstream device. Similarly, if NMI is pressed on the downstream device, since our NMI latch isn't set, we'll ignore it - so the NMI will be trapped by the correct device.
Execution trapper - page-ins
In the following diagram, M1 and MREQ from the CPU is active low. CS from the CPLD is active low, and HLDROMCS is active high.
The execution trapper pages in when an instruction fetch is detected to the following addresses:
- 0x0000 - Conditionally, only on a reset condition.
- 0x0008 - Conditionally, if the RST8EN jumper is closed.
- 0x0066 - Conditionally, only when the NMI button is pressed on our board, or an NMI is fired by the programmable trap.
Page-ins are triggered when the following instruction is decoded:
- CALL 0x3FF8 - 0x3FFF - allowing for a small jump table to access the socket library. Unconditional.
Page-ins for addresses are performed on an "optimistic" basis: HLDROMCS becomes active and A15OUT is held high as soon as M1 is detected active, and the address of interest is on the address bus. M1 goes active half a clock cycle before MREQ goes active. Since the address bus might not yet be stable, the page in isn't yet latched. If the address is still set on the falling edge of MREQ, the page-in is latched. The purpose of this timing scheme is to prevent a glitch on A15OUT, which would occur if it wasn't held high until MREQ became active due to gate propagation delays.
The CALL page-in is a little more complex. If the CALL instruction is detected on the data bus at the end of the M1 cycle, a flip flop is set. If the next bus cycle is a read containing the data 0xF8 to 0xFF, a second flip flop is set (and the CALL flip flop is reset). If the next bus cycle contains 0x3F on the data bus, then the page-in flip flop is set, and the previous is reset (a bit must 'ripple through' the three flip flops for a page in to occur. If any other byte is encountered, the 'ripple' is stopped, and a page in doesn't occur).
Execution trapper - page-outs
Unlike the page-in, pageouts are performed at the tail end of the M1 cycle. Page outs occur on the following execution addresses:
- 0x007C - Unconditional
If the page out address is detected with M1 and MREQ active, on the rising edge of M1, the page-in latch is reset, A15OUT reverts to pass through the A15 signal, and HLDROMCS becomes inactive.
Non maskable interrupt behaviour
The CPLD only responds to an NMI signal generated by the Spectranet's NMI switch; NMIs from downstream devices are passed on unmolested, and the execution of the Z80's NMI vector are not trapped.
When the Spectranet's NMI switch is closed, the NMI event flip flop is set, and the CPLD generates an NMI signal on the Z80's NMI pin via the Spectrum's edge connector. If the event flip flop is set, and execution is detected at address 0x0066 (the NMI vector) a page in by the normal execution trapper method occurs. The NMI flip flop remains set until the CPLD decodes the RETN (ED 45) instruction on the data bus. The NMI event flip flop remains set until RETN (rather than a page out at 0x007C) so that page-outs can occur as part of the NMI handler (for example, to call the Spectrum's BASIC ROM) without causing multiple NMI signals from being sent.
Programmable execution trap
A programmable trap is provided, which allows any address in the Z80's address space to cause a trap. It works differently to the fixed traps, in that it doesn't itself cause a ROM page in (since the trap may not be in an area where ROM can be mapped). It instead triggers a non maskable interrupt. The NMI handler is called using the standard behaviour discussed in the last paragraph. System variables are provided to allow the NMI handler to distinguish between an NMI button press and an execution trap NMI. This is documented on the Trapping execution page.
The execution trap is controlled with a 16 bit value, the little-endian representation of the address to trap. This is sent to the CPLD on port 0x023B, using two OUT instructions. The first OUT (0x023B),... instruction should contain the LSB of the address, and the second OUT (0x023B) instruction should contain the MSB.
To prevent the trap firing when it is not desired, it is enabled and disabled by bit 3 of the control register, which is accessed on port 0x033B. The programmable execution trap will only fire when bit 3 of the control register is set to 1.
The timing for the programmable execution trap is the same as for the fixed traps - the trap will be set on the 'leading edge' of the instruction. The CPU does not respond until the instruction completes, so the program counter (pushed onto the stack) when the CPU responds to the NMI will be the address of the next instruction to be executed after the trap. The NMI logic in the CPLD is set in the same way as when the NMI button is pressed, except the signal remains internal to the CPLD.
Control and Status Register
This register is read/write, and can be accessed on port 0x033B. Programs can read this register to discover information about the CPLD's state, and to enable or disable a function. At present, 4 bits are used, and the remainder have no effect when written, and when read, return a value of 1. When modifying writeable flags in this register, the result must be merged into the exsting value of the register to prevent undesirable effects, so any program that writes the register, should read it first and only change the bits that need to be changed.
The bits are defined as follows. When writing to the port:
- Bit 0 - Page-in bit: set to 1, this causes the Spectranet memory to be paged in.
- Bit 1 - Reserved.
- Bit 2 - Reserved.
- Bit 3 - Programmable trap enable. When set to 1, an NMI is fired whenever the CPU fetches an instruction at the address in the 16-bit register at port 0x023B.
- Bit 4 - Reserved.
- Bit 5 - Deny downstream A15 (set to 1 to prevent peripherals from ever seeing /MREQ + A15 low
- Bit 6, 7 - Reserved.
On power up, bits 0 and 3 are reset.
When reading from the port:
- BIt 0,1,2 - The current border colour
- Bit 3 - Programmable trap enable - set to 1 if the programmable trap is enabled.
- Bit 4 - State of 128K screen memory page
- Bit 5 - Deny downstream A15
- Bit 6 - W5100 interrupt pin state
- Bit 7 - Reserved
The CPLD monitors the border colour sent to port 0xFE so that snapshots of the machine state can be made which have the correct border colour. The border colour can't actually be read from the base Spectrum hardware (except when running BASIC).