Page 2 of 2

Re: Updated notes on an X16 CP/M card

Posted: Mon May 19, 2025 3:32 pm
by BruceRMcF
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:

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
... 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:

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
With the SPI subroutines as:

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 
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.

Re: Updated notes on an X16 CP/M card

Posted: Tue May 20, 2025 4:35 pm
by BruceRMcF
After going through the new SPI boot code the next day, I realize I could properly set and reset the SPI select without bumping the byte transceive past RST $20 {* And I do appreciate that the author of the Zeus Z80 assembly language environment is a believe in "$" notation} ... which means that the /SPI0 select can go onto the inverted latch along with the heartbeat LED and the "/EXT#" SPI selects. That saves a net 1 pin (the A7 address line is still needed for decoding SPI I/O to $80-$FF, but CPLD0 no longer needs an SPI0 output pin).

That means that down the track, it might be possible to set up a selection between SRAM /CS on one pin or on two, so that the Z80 version can have either a single 64KB SRAM or double 32KB SRAM system.

The idea is that since the internal MODE0_RESET status is not needed during the bootloader, then raising the MODE0_RESET when /PROM is still low could be latched on a different internal macrocell as selecting the two-SRAM setup. The "default" /SRAM_CS would be the $8000-$FFFF select in two SRAM mode, and the "alternate" /SRAM_CS2 would be the $0000-$7FFF select, so that the BIOS bootload would be to the correct physical SRAM location.

There's absolutely no squeeze in having a bootloader track for a two 32KB SRAM system set up that hidden mode bit before calling BIOS cold boot, since there is close to 2KB (or possibly more) before the BIOS in the bootloader track.