Saturday, August 15, 2015

GPIO SPI CPLD

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:


; Assume HL is the data to write out to MAX3110E and HL will have the
; exchanged MAX3110E data upon return
; CS# low
    IN A, (0x80)         ; get current GPIOs
    AND A, ~0x01         ; set bit 0 to 0
    OUT (0x80), A        ; write GPIO back out

; exchange H
    OUT (0x83), H        ; load shift register
    OUT (0x84), 0x48     ; shift 8 toward MSB
    NOP                  ; give it time to work
    NOP
    IN H, (0x83)         ; read in first 8 bits from MAX3110E

; exchange L
    OUT (0x83), L        ; load shift register
    OUT (0x84), 0x48     ; shift 8 toward MSB
    NOP                  ; give it time to work
    NOP
    IN L, (0x83)         ; read in last 8 bits from MAX3110E

; CS# high
    IN A, (0x80)         ; get current GPIOs
    OR A, 0x01           ; set bit 0 to 0
    OUT (0x80), A        ; write GPIO back out

    RET


Here is the verilog:

// SPI module
//
//  This CPLD will provide 8 GPIO and limited serial IO for SPI and/or I2C
//      The registers are:
//      0x80: GPIO outvalues
//          A read/write register holding values to be written on GPIO lines
//          that are in output mode
//      0x81: GPIO direction (1=out, 0=in)
//          A read/write register to control the input/output mode of the GPIO
//          lines
//      0x82: GPIO levels
//          A read only register with the current GPIO levels.
//      0x83: shift register (shifted based on control register)
//          A read/write register with data to be shifted in/out
//      0x84: shift control
//          bits 3-0: count
//              0: no shifting
//              1-8: shift by the amount
//              9-15: not supported
//          bit 4: 1=HiZ Dout and use it for input, 0=use Din for input
//              Use 1 for I2C type buses where the clock master controls
//              a bidirectional data line
//              Use 0 for full duplex buses with separate Din and Dout
//          bit 5: 1=shift toward MSB 0=SCK shift toward LSB
//              Use 1 to shift up. If the count is less than 8, the value
//              written to the shift register must be shifted by the CPU
//              Data is read into the LSB
//              Use 0 to shift down. Data is read into the MSB
//          bit 6: 1=SCK goes write high read low 0=SCK goes write low read high
//              Internally, the counter is decremented every 2 pin_CLK. The
//              first beat outputs Dout. The second shifts Din in.
//              Use 0 if the first beat is 0 and the second is 1. i.e. read on
//              rising edge
//              Use 1 if the first beat is 1 and the second is 0. i.e. read on
//              falling edge
//          bit 7: 1=SCK default high, 0=SCK default low
//              Use this bit to set the SCK before and after the actual
//              shift operation
//
//  Usage:
//      0x80, 0x81, and 0x82 control 8 GPIO pins
//      read and write to 0x83 for data shifted in/out
//      Use 0x84 to write a write only count. This will immediately start
//      to shift the shift register in/out. Use a NOP or two to ensure it
//      is complete before reading or writing
//      Use bit5 =1 for 2 wire serial and =0 for 3 wire serial.
//
`define IO_VALUE 5'b10000
`define IO_RANGE 7:3
`define ACTIVELOW 1'b0

module GPIO_SPI(
    // 2MHz clock for CPU
    input pin_CLK,

    // RESET
    input pin_nRESET,

    // CPU ddress bus
    input [7:0] pins_A,

    // CPU data bus
    inout [7:0] pins_D,

    // CPU pins
    input pin_nIORQ,
    input pin_nRD,
    input pin_nWR,
    
    // Pins to UART
    input pin_Din,
    inout pin_Dout,
    output reg pin_SCK,

    // GPIO
    inout [7:0]pins_GPIO
);

    // registers
    reg [7:0] regGpioOutLevel;
    reg [7:0] regGpioDirection;
    reg [7:0] regShift;
    reg [3:0] regCounter;
    reg regDoutIsDin;
    reg regShiftTowardMSB;
    reg regSckHigh;
    reg regDefaultSCK;

    // shift
    reg sck;
    reg Dout;
    wire Din;
    
    // Internal databus for output
    reg [7:0] Dint;

    // IO request in the addressable range of this module
    wire nInRange;

    assign Din = regDoutIsDin ? pin_Dout : pin_Din;
    assign pin_Dout = regDoutIsDin ? 1'bz : Dout;

    // An IO read in the addressable range of this module
    assign nInRange = ((pins_A[`IO_RANGE] == `IO_VALUE)? 1'b0 : 1'b1) | pin_nIORQ;

    // Bidirectional databus control
    assign pins_D = ((pin_nRD | nInRange) == `ACTIVELOW) ? Dint : 8'bzzzzzzzz;

    assign pins_GPIO[0] = regGpioDirection[0] ? regGpioOutLevel[0] : 1'bz;
    assign pins_GPIO[1] = regGpioDirection[1] ? regGpioOutLevel[1] : 1'bz;
    assign pins_GPIO[2] = regGpioDirection[2] ? regGpioOutLevel[2] : 1'bz;
    assign pins_GPIO[3] = regGpioDirection[3] ? regGpioOutLevel[3] : 1'bz;
    assign pins_GPIO[4] = regGpioDirection[4] ? regGpioOutLevel[4] : 1'bz;
    assign pins_GPIO[5] = regGpioDirection[5] ? regGpioOutLevel[5] : 1'bz;
    assign pins_GPIO[6] = regGpioDirection[6] ? regGpioOutLevel[6] : 1'bz;
    assign pins_GPIO[7] = regGpioDirection[7] ? regGpioOutLevel[7] : 1'bz;


    // Keep internal databus updated
    always @(*) begin
        case (pins_A[2:0])
            3'b000: begin
                Dint <= regGpioOutLevel[7:0];
            end
            3'b001: begin
                Dint <= regGpioDirection[7:0];
            end
            3'b010: begin
                Dint <= pins_GPIO[7:0];
            end
            3'b011: begin
                Dint <= regShift[7:0];
            end
            default: begin
                Dint <= 8'bxxxxxxxx;
            end
        endcase
    end

    always @(posedge pin_CLK) begin

        if (pin_nRESET == `ACTIVELOW) begin
            regGpioDirection <= 0;
            regCounter <= 0;
            regDoutIsDin <= 0;
            regShiftTowardMSB <= 0;
            regSckHigh <= 0;
            sck <= 0;
        end
        else begin
            if ((nInRange | pin_nWR) == `ACTIVELOW) begin
                case (pins_A[2:0])
                3'b000: begin
                    regGpioOutLevel[7:0] <= pins_D[7:0];
                end
                3'b001: begin
                    regGpioDirection[7:0] <= pins_D[7:0];
                end
                3'b011: begin
                    regShift[7:0] <= pins_D[7:0];
                end
                3'b100: begin
                    regCounter[3:0] <= pins_D[3:0];
                    regDoutIsDin <= pins_D[4];
                    regShiftTowardMSB <= pins_D[5];
                    regSckHigh <= pins_D[6];
                    regDefaultSCK = pins_D[7];
                    sck <= 1'b0;
                end
                endcase
                pin_SCK <= regDefaultSCK;
            end
            else if ((|regCounter) == 1'b1) begin
                if (sck == 1'b0) begin
                    Dout <= regShiftTowardMSB ? regShift[7] : regShift[0];
                end
                else begin
                    regCounter <= regCounter - 1;
                    if (regShiftTowardMSB) begin
                        regShift <= { regShift[6:0], Din };
                    end
                    else begin
                        regShift <= { Din, regShift[7:1] };
                    end
                end
                sck <= ~sck;
                pin_SCK <= sck ^ regSckHigh;
            end
            else begin
                pin_SCK <= regDefaultSCK;
            end
        end
    end

endmodule

No comments:

Post a Comment