As far as working with CPLD's, not a thing, I'm a software guy ~ hence the breadboarding. If I could work out a circuit that only needs 8v16 or 10v22 SPLD's, I would prefer it.
Which is a segue into:
(2) Approach Two: A Genuine Co-Coprocessor
The
reason that the CP/M cards/cartridges in the 80s took over the system bus was because
RAM was expensive. But above, I just casually cranked RAM from 32KB to 128KB. Why not 64KB? Because the 128KBx8 is more readily available.
Looking at mouser, I see 128KB (1Mbit in an byte wide layout) in the $3-$3.25 range, Q1. If you have a two IC board, plus glue logic and SPLDs, and the (up to 20MHz) processor costs about $11 (new, you might be able to get pulls substantially cheaper, if there's delays in stocking the Z80), why would you kick at spending $3.25 for 128KB RAM? For not much more you can add a button battery carrier with integrated trickle supply circuit to make the data in the RAM persistent.
So having the Z80 and either (CP/M Plus style) 1 common 32KB area and 3 32KB banks, or simply (for CP/M 2.2) two distinct 64KB memory spaces, why have the Z80 take over the CX16 system bus? Which not run it as a co-processor system, with the Z80 acting as a client and a support services program running on the CX16?
I was looking at the Z80-MBC2
https://hackaday.io/project/159973-z80-mbc2-a-4-ics-homebrew-z80-computer and the way they get a freestanding Z80 in 4 ICs is that one of the ICs is an AtMega32A which acts as EPROM and universal I/O emulator. The 4th IC is glue logic, a quad NAND gate, which handles the swapping the virtual-EPROM and the SRAM into the Z80 memory map.
But if you have a CX16, there is no need for the Arduino ... with the right system, the CX16 can act as the universal I/O emulator, and the terminal, and can be given the ability to inject data directly into the Z80 RAM making an EPROM unnecessary.
Which brings me to the CX16 User Port, which uses free VIA GPIO and Port A handshaking lines to offer a standard Parallel Port interface. But then an EPP interface can be implemented, and it doesn't even need as many lines:
https://pinout.net/browse.php?conid=156 EPP is very simple. There are 8 data lines. There are Reset, R/W, /Data and /Address lines out, a reset line n interrupt line in, /ACK and the BUSY line in that in EPP acts as a handshake line that signals availability to transfer data when low, and completion of transfer when high.
The current CX16 User Port puts a VIA Port A handshake line on the standard parallel port STROBE ... since the standard parallel port is write-only ... so it might be necessary to swap out the R/W line with one of the unused paralell port inputs, making it a "almost but not quite" standard EPP port.
In EPP, you set R/W for the operation you want and pull down either /Data or /Address, then the device pulls down the BUSY line when it is ready, then you read or write the data lines, and then repeat the process. It is designed with an IDE interface in mind, so the /Address refers to the IDE register address. So you can use it to read and write IDE registers and when they are set up the way you want them, you can read and write Data.
For the Z80 as a pure slave co-processor with it's own RAM, this is all that is really needed.
The idea is to have an auto-increment data read or write that works in batches of 128 bytes. This is a standard CP/M sector. The
"Reset" line resets the auto-increment index to 0. Then there is a latch that holds an eight byte memory address segment value that the auto-increment index extends. That can cover 32KB. A register /Address write to register 0-3 sets the memory address segment, with the two bits of the /Address setting which 32KB is targeted.
So to boot up for the first time, you write what you need to write in the first 128 bytes of memory and then the CCP (or substitute), BDOS (or substitute) and BIOS in high RAM and away you go.
The last four register addresses are for the Z80 reset and for setting the Z80 memory bank, 04h pulls the /RESET line low and 05h pulls the /RESET line high for the 64K Memory Bank 0, and 06h and 07h does the same for the 64K Memory Bank 1.
Finally, a Z80 access to I/O address 0FFh pulls the /ACK line high, to signal to the CX16 that there is a BIOS function request (for the simplest version of the system that could be any I/O access, but one way to use the Z80 box is as an I/O expansion handler, in which case you would want to split off the I/O page into zones with, say, a 3 to 8 decoder, and invert the /Dev7 select as the /ACK line, allowing 7 devices plus the BIOS signal. Setting the trigger access at 0FFh means the BIOS won't have to be rewritten for that kind of I/O box.
So the BIOS writes whatever information is needed by the CX16 to perform the BIOS function (including a leading byte to identify which BIOS function it is) at 0FF80, and then writes to I/O 0FFh, the CX16 gets the interrupt (or polls the state of the /ACK bit), sets the data address to $FF for the register "address" 1 or 3, reads the data, performs the function, and goes back to write a $FF at the Z80 address 0FF80h. The Z80 BIOS busy loops on 0FF80 until it is 0FFh, and then completes the BIOS call.
The glue logic needed to be added to the Z80 and 128KB RAM is fairly simple. /Data is connected to Z80 /BusRequest, and combined with the R/W line turns on an 8bit line I/O transceiver in the correct direction. BUSY is connected to the AND of /BUSACK and the /RESET line, so memory can be written in normal mode when the Z80 is running as well as when the Z80 is idle with /RESET low. AND(/Address,D2) is the /Select for the segment address latch and the memory configuration latch of D0 and D1. AND(/Address,NOT(D2)) is the /Select for the latch that holds the /RESET state and the active memory bank.
The segment memory address and extension memory segment latches are tri-state latches, and their output is selected by /ACK, which will only be low when either reset is low or /BusAck is low, when the Z80 address lines are in high impedance. The /RESET, Memory Bank latch is a blind latch that is always on, hooked directly to the RESET line and to A16 of the 128KB RAM. There is also an 8bit counter for the low 7bits of the address during /DATA mode, with its reset connected to /RESET.
The /RESET latch will be in 0h state on power up, so the box will not have a free running Z80 when it powers up. If the boot has already been written and is preserved by the button battery, the CX16 server only has to write 05h or 07h to the address and it is ready to go. On first power up, or if cleaning up after a program crash, the CX16 writes the Boot up data to the correct location in the desired RAM locations, and then releases the reset in the desired RAM bank.
In pursuing a project like this, the BIOS would be a very small shim, which just writes down what it wants to happen in the communication segment and then waits until it happens. So this means the maximum amount of re-use of facilities already written into the CX16 Kernel ... and also means that the bulk of the programming is programming the server program.
So this would get a really responsive Z80 CP/M system (or CP/Mish system), to run Pascal compilers or APL, or Small-C, or Mix C, or (etc.) ... with a very modest amount of very straightforward Z80 assembly language and all of the harder support programming on the CX16. So this would be more my kind of project if nobody else does a CP/M board in the ultra-cheap space.