In order to connect the UART (MAX3110E) part to the Z-80, I’ll use a CPLD that does SPI. My first CPLD plan was fairly specific to the SPI of the UART part. But, it makes more sense to make something a little more general purpose. So, I came up with the following verilog design.

This design should allow me to do not just SPI but also I2C. And it has 8 GPIO.

The MAX3110E requires that its CS goes low while SCLK is low. Then a 16 bit data exchange takes place. Then while SCLK is low, CS goes high. It updates Dout and expects Din to be updated on SCLK falling edge and clocks in and expects the user to clock in on the rising edge. Sixteen times.

So this will be able to do it by using a GPIO as CE.

Here some example Z-80 assembly that would use the CPLD to communicate with the MAX3110E:

 1 ; Assume HL is the data to write out to MAX3110E and HL will have the
 2 ; exchanged MAX3110E data upon return
 3 ; CS# low
 4     in a, (#0x80)         ; get current GPIOs
 5     and a, ~0x01         ; set bit 0 to 0
 6     out (#0x80), a        ; write GPIO back out
 7 
 8 ; exchange H
 9     out (#0x83), h        ; load shift register
10     out (#0x84), #0x48    ; shift 8 toward MSB
11     nop                  ; give it time to work
12     nop
13     in h, (#0x83)         ; read in first 8 bits from MAX3110E
14 
15 ; exchange L
16     out (#0x83), l        ; load shift register
17     out (#0x84), #0x48    ; shift 8 toward MSB
18     nop                  ; give it time to work
19     nop
20     in l, (#0x83)         ; read in last 8 bits from MAX3110E
21 
22 ; CS# high
23     in a, (#0x80)         ; get current GPIOs
24     or a, #0x01           ; set bit 0 to 0
25     out (#0x80), a        ; write GPIO back out
26 
27     ret

Here is the verilog:

  1 // SPI module
  2 //
  3 //  This CPLD will provide 8 GPIO and limited serial IO for SPI and/or I2C
  4 //      The registers are:
  5 //      0x80: GPIO outvalues
  6 //          A read/write register holding values to be written on GPIO lines
  7 //          that are in output mode
  8 //      0x81: GPIO direction (1=out, 0=in)
  9 //          A read/write register to control the input/output mode of the GPIO
 10 //          lines
 11 //      0x82: GPIO levels
 12 //          A read only register with the current GPIO levels.
 13 //      0x83: shift register (shifted based on control register)
 14 //          A read/write register with data to be shifted in/out
 15 //      0x84: shift control
 16 //          bits 3-0: count
 17 //              0: no shifting
 18 //              1-8: shift by the amount
 19 //              9-15: not supported
 20 //          bit 4: 1=HiZ Dout and use it for input, 0=use Din for input
 21 //              Use 1 for I2C type buses where the clock master controls
 22 //              a bidirectional data line
 23 //              Use 0 for full duplex buses with separate Din and Dout
 24 //          bit 5: 1=shift toward MSB 0=SCK shift toward LSB
 25 //              Use 1 to shift up. If the count is less than 8, the value
 26 //              written to the shift register must be shifted by the CPU
 27 //              Data is read into the LSB
 28 //              Use 0 to shift down. Data is read into the MSB
 29 //          bit 6: 1=SCK goes write high read low 0=SCK goes write low read high
 30 //              Internally, the counter is decremented every 2 pin_CLK. The
 31 //              first beat outputs Dout. The second shifts Din in.
 32 //              Use 0 if the first beat is 0 and the second is 1. i.e. read on
 33 //              rising edge
 34 //              Use 1 if the first beat is 1 and the second is 0. i.e. read on
 35 //              falling edge
 36 //          bit 7: 1=SCK default high, 0=SCK default low
 37 //              Use this bit to set the SCK before and after the actual
 38 //              shift operation
 39 //
 40 //  Usage:
 41 //      0x80, 0x81, and 0x82 control 8 GPIO pins
 42 //      read and write to 0x83 for data shifted in/out
 43 //      Use 0x84 to write a write only count. This will immediately start
 44 //      to shift the shift register in/out. Use a NOP or two to ensure it
 45 //      is complete before reading or writing
 46 //      Use bit5 =1 for 2 wire serial and =0 for 3 wire serial.
 47 //
 48 `define IO_VALUE 5'b10000
 49 `define IO_RANGE 7:3
 50 `define ACTIVELOW 1'b0
 51 
 52 module GPIO_SPI(
 53     // 2MHz clock for CPU
 54     input pin_CLK,
 55 
 56     // RESET
 57     input pin_nRESET,
 58 
 59     // CPU ddress bus
 60     input [7:0] pins_A,
 61 
 62     // CPU data bus
 63     inout [7:0] pins_D,
 64 
 65     // CPU pins
 66     input pin_nIORQ,
 67     input pin_nRD,
 68     input pin_nWR,
 69     
 70     // Pins to UART
 71     input pin_Din,
 72     inout pin_Dout,
 73     output reg pin_SCK,
 74 
 75     // GPIO
 76     inout [7:0]pins_GPIO
 77 );
 78 
 79     // registers
 80     reg [7:0] regGpioOutLevel;
 81     reg [7:0] regGpioDirection;
 82     reg [7:0] regShift;
 83     reg [3:0] regCounter;
 84     reg regDoutIsDin;
 85     reg regShiftTowardMSB;
 86     reg regSckHigh;
 87     reg regDefaultSCK;
 88 
 89     // shift
 90     reg sck;
 91     reg Dout;
 92     wire Din;
 93     
 94     // Internal databus for output
 95     reg [7:0] Dint;
 96 
 97     // IO request in the addressable range of this module
 98     wire nInRange;
 99 
100     assign Din = regDoutIsDin ? pin_Dout : pin_Din;
101     assign pin_Dout = regDoutIsDin ? 1'bz : Dout;
102 
103     // An IO read in the addressable range of this module
104     assign nInRange = ((pins_A[`IO_RANGE] == `IO_VALUE)? 1'b0 : 1'b1) | pin_nIORQ;
105 
106     // Bidirectional databus control
107     assign pins_D = ((pin_nRD | nInRange) == `ACTIVELOW) ? Dint : 8'bzzzzzzzz;
108 
109     assign pins_GPIO[0] = regGpioDirection[0] ? regGpioOutLevel[0] : 1'bz;
110     assign pins_GPIO[1] = regGpioDirection[1] ? regGpioOutLevel[1] : 1'bz;
111     assign pins_GPIO[2] = regGpioDirection[2] ? regGpioOutLevel[2] : 1'bz;
112     assign pins_GPIO[3] = regGpioDirection[3] ? regGpioOutLevel[3] : 1'bz;
113     assign pins_GPIO[4] = regGpioDirection[4] ? regGpioOutLevel[4] : 1'bz;
114     assign pins_GPIO[5] = regGpioDirection[5] ? regGpioOutLevel[5] : 1'bz;
115     assign pins_GPIO[6] = regGpioDirection[6] ? regGpioOutLevel[6] : 1'bz;
116     assign pins_GPIO[7] = regGpioDirection[7] ? regGpioOutLevel[7] : 1'bz;
117 
118 
119     // Keep internal databus updated
120     always @(*) begin
121         case (pins_A[2:0])
122             3'b000: begin
123                 Dint <= regGpioOutLevel[7:0];
124             end
125             3'b001: begin
126                 Dint <= regGpioDirection[7:0];
127             end
128             3'b010: begin
129                 Dint <= pins_GPIO[7:0];
130             end
131             3'b011: begin
132                 Dint <= regShift[7:0];
133             end
134             default: begin
135                 Dint <= 8'bxxxxxxxx;
136             end
137         endcase
138     end
139 
140     always @(posedge pin_CLK) begin
141 
142         if (pin_nRESET == `ACTIVELOW) begin
143             regGpioDirection <= 0;
144             regCounter <= 0;
145             regDoutIsDin <= 0;
146             regShiftTowardMSB <= 0;
147             regSckHigh <= 0;
148             sck <= 0;
149         end
150         else begin
151             if ((nInRange | pin_nWR) == `ACTIVELOW) begin
152                 case (pins_A[2:0])
153                 3'b000: begin
154                     regGpioOutLevel[7:0] <= pins_D[7:0];
155                 end
156                 3'b001: begin
157                     regGpioDirection[7:0] <= pins_D[7:0];
158                 end
159                 3'b011: begin
160                     regShift[7:0] <= pins_D[7:0];
161                 end
162                 3'b100: begin
163                     regCounter[3:0] <= pins_D[3:0];
164                     regDoutIsDin <= pins_D[4];
165                     regShiftTowardMSB <= pins_D[5];
166                     regSckHigh <= pins_D[6];
167                     regDefaultSCK = pins_D[7];
168                     sck <= 1'b0;
169                 end
170                 endcase
171                 pin_SCK <= regDefaultSCK;
172             end
173             else if ((|regCounter) == 1'b1) begin
174                 if (sck == 1'b0) begin
175                     Dout <= regShiftTowardMSB ? regShift[7] : regShift[0];
176                 end
177                 else begin
178                     regCounter <= regCounter - 1;
179                     if (regShiftTowardMSB) begin
180                         regShift <= { regShift[6:0], Din };
181                     end
182                     else begin
183                         regShift <= { Din, regShift[7:1] };
184                     end
185                 end
186                 sck <= ~sck;
187                 pin_SCK <= sck ^ regSckHigh;
188             end
189             else begin
190                 pin_SCK <= regDefaultSCK;
191             end
192         end
193     end
194 
195 endmodule