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