Re: Updated notes on an X16 CP/M card
Posted: Mon May 19, 2025 3:32 pm
SPI Updated
Well, OK, "sketching out the design model" is as far as it can go, but diving into learning CUPL to use WinCUPL once I get back to a computer where I can install it has led to an update to the SPI model.
One advantage of the "Write SPI data, Read SPI data" to generate an SPI cycle is that it cannot be cycled by accident (say, if interrupts are added to the system), as the earlier "Write, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, Read" approach could be.
But since both OUT ($80),A and IN A,($80) take three m-cycles (read prefix instruction byte, read I/O address byte, perform I/O operation), and these m-cycles are 4, 3, and 4 system clock cycles, they are each 11 clock-cycle operations. So it would be an appreciable increase in bandwidth to get the SPI cycle performed in fewer than 22 clock cycles.
Another advantage of the above approach, which would be good to retain, is that the two phases of the serial clock are symmetric, and the hidden "latch MISO" phase is reasonably close to the rising serial clock, so it would be able to work with a wide range of SPI chips at higher clock frequencies than the "NOP" approach, which just used the Refresh line cycle as the serial clock until raised by the Read of the SPI byte.
However, looking at example code of CUPL shift registers, and getting more in depth information on using the ATF1504 I/O pins as in/out pins, I realized that each "IN A,(C)" instruction can run an entire SPI cycle. The Write operation to I/O address $80 can push the serial clock low as it loads the shift register and MOSI. Then the next following Refresh cycle (while SCLK is low) can Raise the serial clock, and shift the register, shifting MISO in. Then the Read operation (in the third m-cycle) puts the shift register values on the data bus, pushes the serial low, and latches MOSI from the shift register bit7.
In order for the final IN instruction to avoid starting a new SPI cycle, what is written into the SCK3 register during I/O accesses of $80-$FF is the value of A0, so that an I/O access to $80 will start a new cycle, but an I/O access to $81 will not.
Now, this means that the entire SPI cycle is completed over multiple instruction opcode sequences, but there is still no problem with this sequence being interrupted. If interrupted, then the first instruction in the interrupt completes the SPI cycle, and then the SPI serial clock is high, with MOSI valid and MISO for that cycle latched, until the interrupt returns and the IN A,($80) is executed to push the SPI clock low again,
In the sequence:
... SCLK3 is an SR latch, preset high at system reset, which goes low when /IOREQ, PHI2 and A0 are low and A7 is high, and goes high when /MREQ, /REFRESH and /SCLK3 are all low, and PHI2 is high. This means it goes low in the middle of the 2nd t-cycle in the I/O read, (clock 10 of the 11 clock instruction), and goes high at the start of the 4th t-cycle of the following instruction. So that is idle high, low for 5.5 t-cycles and then high for at least 5.5 t-cycles (6.5 if OUT (C),r or IN r,(C) is used).
The optimized, fully unrolled serial clock cycle is a symmetric cycle over 11 system clock cycles, so the effective speed rating required for SPI ICs/Devices is 1/11th of the system clock. With a 20MHz Z180 the fastest feasible part for this design, that means any 2MHz or faster rated SPI IC/Device can be placed on this bus without worrying about the speed of the serial clock.
This also means that the SPI transceive subroutine can fit within 32 bytes, so it can be located at 0020h, which makes for a larger number of "NOP" instructions padding out both the main routine and SPI transceiver subroutine code in the bootloader. On a side note, I also realized I was using the wrong OUT/IN codes -- OUT (C),r and IN r,(C) are for registers other than A or when device support code allows changing the I/O address ... with a known address and data sent & returned in A, OUT (n),A and IN A,(n) are both 1 cycle faster:
With the SPI subroutines as:
Note that the final 11 bytes of each part are NOP's, so that addresses %x01101..%x01111 and %x11000...%x11111 all map to $00, which will, hopefully, make it easier for the fitter to fit the bootloader code.
Well, OK, "sketching out the design model" is as far as it can go, but diving into learning CUPL to use WinCUPL once I get back to a computer where I can install it has led to an update to the SPI model.
One advantage of the "Write SPI data, Read SPI data" to generate an SPI cycle is that it cannot be cycled by accident (say, if interrupts are added to the system), as the earlier "Write, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, Read" approach could be.
But since both OUT ($80),A and IN A,($80) take three m-cycles (read prefix instruction byte, read I/O address byte, perform I/O operation), and these m-cycles are 4, 3, and 4 system clock cycles, they are each 11 clock-cycle operations. So it would be an appreciable increase in bandwidth to get the SPI cycle performed in fewer than 22 clock cycles.
Another advantage of the above approach, which would be good to retain, is that the two phases of the serial clock are symmetric, and the hidden "latch MISO" phase is reasonably close to the rising serial clock, so it would be able to work with a wide range of SPI chips at higher clock frequencies than the "NOP" approach, which just used the Refresh line cycle as the serial clock until raised by the Read of the SPI byte.
However, looking at example code of CUPL shift registers, and getting more in depth information on using the ATF1504 I/O pins as in/out pins, I realized that each "IN A,(C)" instruction can run an entire SPI cycle. The Write operation to I/O address $80 can push the serial clock low as it loads the shift register and MOSI. Then the next following Refresh cycle (while SCLK is low) can Raise the serial clock, and shift the register, shifting MISO in. Then the Read operation (in the third m-cycle) puts the shift register values on the data bus, pushes the serial low, and latches MOSI from the shift register bit7.
In order for the final IN instruction to avoid starting a new SPI cycle, what is written into the SCK3 register during I/O accesses of $80-$FF is the value of A0, so that an I/O access to $80 will start a new cycle, but an I/O access to $81 will not.
Now, this means that the entire SPI cycle is completed over multiple instruction opcode sequences, but there is still no problem with this sequence being interrupted. If interrupted, then the first instruction in the interrupt completes the SPI cycle, and then the SPI serial clock is high, with MOSI valid and MISO for that cycle latched, until the interrupt returns and the IN A,($80) is executed to push the SPI clock low again,
In the sequence:
Code: Select all
OUT ($80),A
IN A,($80)
IN A,($80)
IN A,($80)
IN A,($80)
IN A,($80)
IN A,($80)
IN A,($80)
IN A,($81)
RET
The optimized, fully unrolled serial clock cycle is a symmetric cycle over 11 system clock cycles, so the effective speed rating required for SPI ICs/Devices is 1/11th of the system clock. With a 20MHz Z180 the fastest feasible part for this design, that means any 2MHz or faster rated SPI IC/Device can be placed on this bus without worrying about the speed of the serial clock.
This also means that the SPI transceive subroutine can fit within 32 bytes, so it can be located at 0020h, which makes for a larger number of "NOP" instructions padding out both the main routine and SPI transceiver subroutine code in the bootloader. On a side note, I also realized I was using the wrong OUT/IN codes -- OUT (C),r and IN r,(C) are for registers other than A or when device support code allows changing the I/O address ... with a known address and data sent & returned in A, OUT (n),A and IN A,(n) are both 1 cycle faster:
Code: Select all
; 32 bytes, including padding to RST $20.
; SPI0 starts selected at /Reset
; Set up target:
LD HL,BOOTCODE ; 3 bytes
; Send Read command
LD A,$30 ; 2 bytes
CALL TXSPIBOOT ; 3 bytes
RST $20 ; 1 byte ; $00....
RST $20 ; 1 byte ; $..00..
RST $20 ; 1 byte ; $....00
PAGELD:
RST $20 ; 1 byte
LD (HL),A ; 1 byte
INC L ; 1 byte
JR NZ,PAGELD ; 2 bytes
INC H ; 1 byte
JR NZ,PAGELD ; 2 bytes
JP BOOTCODE ; 3 bytes
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
Code: Select all
; RST20:
LD A,0 ; 2 bytes
TXSPIBOOT:
; SPI byte to output in A
OUT ($80),A ; 2 bytes
IN A,($80) ; 2 bytes
IN A,($80) ; 2 bytes
IN A,($80) ; 2 bytes
IN A,($80) ; 2 bytes
IN A,($80) ; 2 bytes
IN A,($80) ; 2 bytes
IN A,($80) ; 2 bytes
IN A,($81) ; 2 bytes
RET ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte
NOP ; 1 byte