Retro CP/M internal Userbox card design note ...

Feel free to talk about any other retro stuff here including Commodore, Sinclair, Atari, Amstrad, Apple... the list goes on!
Post Reply
BruceRMcF
Posts: 222
Joined: Sat Jan 07, 2023 10:33 pm

Retro CP/M internal Userbox card design note ...

Post by BruceRMcF »

This is a follow-up of the A retro CP/M card for the new X16 Expansion Slot? topic.

For one thing, along the way in that forum, the approach slid away from an expansion card to a card that is accessed via SPI using the four spare lines in VIA#1, which are available via a header near VIA#1, and +5v and GND which are available via the Controller #3 & #4 header. For one thing, while having the spare VIA#1 jumper on the X16 Console board is not guaranteed, it's more likely than two or more expansion slots on the X16 Console board.

I will probably target 2MHz to 6MHz at the outset for breadboarding, but if it progresses to an SBC, there are 20MHz DIP and surface mount Z80 chips available, and 33MHz Z180 chips, so the clock target for the SBC version would be 20MHz for the Z80-based CP/M 2.2 board and 32MHz for a Z180 based CP/M Plus follow-up board. The target would be to follow the common CP/M system approach of a terminal system, so the connection to the X16 would be a built-in X16 side SPI UART chip (MAX3100).

I've moved away from ROMless ideas, since a FlashROM can be programmed by an RPi Zero on a breadboard, and a NVRAM can be set up to be programmed in circuit.

But even more, I've swapped over the way that the RAMdisk is done, to take advantage of the Z80 OTIR and INIR instructions. These work by decrementing B, then either moving the byte from (DE) in RAM to (C) in I/O, or (C) in I/O to (DE), then increment DE, then repeat the instruction if B is not 0. So to push 128 bytes into an address in the I/O page, put 128 into B, set up C and DE and execute the OTIR.

In the I/O operations based on (C) address of the I/O location, the contents of B are put on the processor A8-A15 address lines.

So I am sketching in up a 64KB SRAM for system memory, and a 512KB SRAM for the RAMdisk -- "RAM2", designed to be accessed via the OTIR and INIR instructions. 1 sector is 128 bytes, so A8-A14 connects to the bottom lines of RAM2. Using the top half of the I/O page for the 128 sectors per "track" means that A0-A6 connects to the next seven lines of RAM2. Then five lines in a hex latch holds the "track" address for the 32 "tracks".

SO in this post, let me look at a sketch of how the 2KB-32KB ROM, 64KB SRAM, and 512KB SRAM are accessed by a Z80.

Not everything changes as dramatically. Two latches, one to control SPI selects & a heartbeat LED, and one for the RAM/ROM select and the five bits of the "track" address in the other.

LATCH1_Q1 =: RAM2_A14
LATCH1_Q2 =: RAM2_A15
LATCH1_Q3 =: RAM2_A16
LATCH1_Q4 =: RAM2_A17
LATCH1_Q5 =: RAM2_A18
LATCH1_Q6 =: OR2_1C ; RAM/ROM

; ROM shows up in the bottom 32KB of memory when the RAM/ROM is low.
; So when /MEMREQ = Z80_A15 = ROM/RAM, the ROM is selected
; This can be done with a 3 input OR, "OR2"

OR2_1A := Z80_A14
OR2_1B := /Z80_MEMREQ
OR2_1C := LATCH1_Q6 ; = RAM/ROM
OR2_1Y =: /ROM_CS

/ROM_CS := OR2_1Y
/ROM_OE := /Z80_RD
ROM_A0-A14 := Z80_A0-A14
ROM_D0-D7 :=: Z80_D0-D7

; A 64KB SRAM has an active high /CS1 and an active low CS2:

/RAM1_CS1 := /Z80_MEMREQ
RAM1_CS2 := /ROM_CS
/RAM1_OE := /Z80_RD
/RAM1_WE := /Z80_WR
RAM1_A0-A15 := Z80_A0-A15
RAM1_D0-D7 :=: Z80_D0-D7

; Note that RAM2 is customized to OTIR and INIR. INIR will write
; the bottom of the sector to the top of the RAM region and the
; top of the sector to the bottom of the RAM region, but OTIR does
; the same thing, so there is no programming complexity.

; The RAM2 can be selected by the OR of /IOREQ and the invert of A7:

NOT1_1A := Z80_A7
NOT1_1Y =: OR1_1A

OR1_1A := NOT1_1Y ; /A7
OR1_1B := /Z80_IOREQ
OR1_1Y =: /RAM2_CS

/RAM2_CS := OR1_1Y
/RAM2_OE := /Z80_RD
/RAM2_WE := /Z80_WR
RAM2_A0-A6 := Z80_A8-A14
RAM2_A7-A13 := Z80_A0-A6
RAM2_A14-A18 := LATCH1_Q0-Q5

; while the hardware doesn't care, in order to have block indices of 0-255,
; 512KB requires blocks to be 2KB. This implies 16 sectors per block.
; 256 blocks over 32 tracks is 8 blocks per track.
; 80h-8Fh: Block T*8 + 0
; 90h-9Fh: Block T*8 + 1
; A0h-AFh: Block T*8 + 2
; B0h-BFh: Block T*8 + 3
; C0h-CFh: Block T*8 + 4
; D0h-DFh: Block T*8 + 5
; E0h-EFh: Block T*8 + 6
; F0h-FFh: Block T*8 + 7
Last edited by BruceRMcF on Sat Feb 17, 2024 9:56 pm, edited 1 time in total.
BruceRMcF
Posts: 222
Joined: Sat Jan 07, 2023 10:33 pm

Re: Retro CP/M internal Userbox card design note ...

Post by BruceRMcF »

OK, so about those latches.

This is a little bit funky. During the Memory refresh cycle, when /Z80_REFRESH is asserted low during the instruction cycle when the state machine inside the original Z80 was often busy doing the internal part of the instruction, the contents of the Memory Refresh buffer are asserted on A0-A7. At the same time, the contents of the Interrupt Register are asserted on A8-A15. /MEMREQ is pulled low part of this cycle, but with neither /RD nor /WR pulled low.

Now, the /MEMREQ cycle is de-asserted while the contents of the address lines are still valid.

So to get a side path to write a latch, independent of both the 64KB Memory Space and the 256 byte I/O space, you can write the value you want into the Interrupt Register, and if the latch is tied to the address lines and triggered during the correct part of the Memory Refresh cycle.

I use a 2->4 decoder for the SPI circuit, intended to access an SPI UART, serial FlashRAM, and an SPI RTC, and since a common 2->4 decoder is a dual part, I am sketching the other side of the decoder in as the Latch selector. Bit7 of the Interrupt Register is the active high Latch Write Enable, and if enabled, Bit6 selects between the RAM status latch and the SPI device select latch.

; First, OR the /Z80_REFRESH and the /Z80_MEMREQ, so the selection is de-asserted while I is on A8-A15.
; This uses the second gate of the 2-input OR1.

OR1_2A := /Z80_MEMREQ
OR1_2B := /Z80_REFRESH
OR1_2Y =: /DECODE1_1G

; Each side of the decoder has a single active low select, /#G, two address inputs
; and four active low outputs, for %00, %01, %10, and %11. To make the high bit
; an enable, do not connect the first two selects.

; These are clocked latches, so releasing the /G select latches the data.

/DECODE1_1G := OR1_2Y
DECODE1_1A := Z80_A14
DECODE1_1B := Z80_A15
DECODE1_1Y0 =: DNC
DECODE1_1Y2 =: DNC
DECODE1_1Y3 =: /LATCH1_CLK
DECODE1_1Y4 =: /LATCH2_CLK

; RAM1/RAM2 status
/LATCH1_MR := /Z80_RESET
/LATCH1_CLK := DECODE1_1Y2
LATCH1_D0 := Z80_A8
LATCH1_D1 := Z80_A9
LATCH1_D2 := Z80_A10
LATCH1_D3 := Z80_A11
LATCH1_D4 := Z80_A12
LATCH1_D5 := Z80_A13
LATCH1_Q0 =: RAM2_A14
LATCH1_Q1 =: RAM2_A15
LATCH1_Q2 =: RAM2_A16
LATCH1_Q3 =: RAM2_A17
LATCH1_Q4 =: RAM2_A18
LATCH1_Q5 =: RAM/ROM

; LATCH2
/LATCH2_MR := /Z80_RESET
/LATCH2_CLK := DECODE1_1Y3
LATCH2_D0 := Z80_A8
LATCH2_D1 := Z80_A9
LATCH2_D2 := Z80_A10
LATCH2_D3 := Z80_A11
LATCH2_D4 := Z80_A12
LATCH2_D5 := Z80_A13
LATCH2_Q0 =: /SPI_SEL0
LATCH2_Q1 =: /SPI_SEL1
LATCH2_Q2 =: /SPI_SEL2
LATCH2_Q3 =: /SPI_SEL3
LATCH2_Q4 =: /SPI_SEL4
LATCH2_Q5 =: /LED1 ; heartbeat

; Note that both latches reset to 0. This is correct for Latch1, since track0 is the
; system track, to be written to C0h-FFh by the boot-up routine at the beginning of
; ROM. It is not correct for Latch2, so the very first operation in the reset routine
; in ROM would be:
; LD A,FFh
; LD I,A
; INC A
; LD I,A
; ...
; which will set Latch2 to all high then turn off the latch write enable.
TomXP411
Posts: 1719
Joined: Tue May 19, 2020 8:49 pm

Re: Retro CP/M internal Userbox card design note ...

Post by TomXP411 »

So I'm a little unclear... is this going to be a terminal style solution, accessed via the SPI pins, or is it going to talk to VERA and use the host keyboard directly?

Because I'm good either way... I'd love to have a CP/M option on X16.
BruceRMcF
Posts: 222
Joined: Sat Jan 07, 2023 10:33 pm

Re: Retro CP/M internal Userbox card design note ...

Post by BruceRMcF »

Terminal ... an SPI UART on the X16 side talking to an SPI UART on the Z80 side. The Z80 runs as fast as the processor / RAM / ROM allows. That's supposed to be what lets me start breadboarding this summer without having a Dev Board at hand ... I can just use a USB-TTL Serial adapter cable with my laptop as the terminal.

This is aiming to include a serial FlashRAM rather than an SD card port, using the serial FlashRAM as a virtual disk caddy (16 disks from an 8MB x 8 internal layout serial FlashRAM), and with the ROM having a terminal file transfer utility to allow loading the CP/M files from the CX16 running as the terminal.

Also if the 512KB SRAM is battery backed, the first iteration only requires the SPI UART on the Z80 side ... the serial FlashRAM and RTC can be brought in incrementally.

Changing to a pair of 512KB RAMdisks for greater convenience in CP/M operations (eg, one Turbo Pascal or Camel Forth boot-able system disk and several project work disks available in the caddy) would entail adding a bit latch on A7, with the disk select done using the top bit of the Refresh register (as the refresh register increments from 00h to 7Fh then wraps around, so the top bit of the register is never touched by the refresh cycling). However, the 1MB SRAM I see are not in a DIP form factor, so that is an incremental improvement task if it were to ever get to a printed circuit board phase.

Note that with two external SPI selects, an external SPI hub can be made by using one line to enable the hub, and the other to toggle the between writing a byte of hub select bits and reading/writing with the selected device on the hub. Similarly with a three wire or four wire serial device that is not good at sharing an SPI bus, an enable line can be used to detach or attach the device to the SPI bus and the other line used as its select line. So to my mind, for something aiming to be a fairly minimal board, "the number of internal devices plus 2" is a reasonable number of SPI select lines.
BruceRMcF
Posts: 222
Joined: Sat Jan 07, 2023 10:33 pm

Re: Retro CP/M internal Userbox card design note ...

Post by BruceRMcF »

The greatest amount of glue logic is allocated to the SPI interface. The SPI interface is a middle ground between a "write a byte / chip does the SPI / read a byte" approach and pure bit banging. The way it works is that the output byte is written into a MOSI latch, then eight OUT instructions to the correct I/O location generates eight clock cycles, then the latch that supports converting the Mode3 SPI clock to a Mode 0 SPI clock is reset with a read from the clock cycling location, then the data byte is read.

Code: Select all

SPI_TRX:
    OUT (70h),A
    OUT (71h),A
    OUT (71h),A
    OUT (71h),A
    OUT (71h),A
    OUT (71h),A
    OUT (71h),A
    OUT (71h),A
    OUT (71h),A
    IN A,(71h)
    IN A,(70h)
    RET
Note that the Mode0 reset is not optional, since one of the two shift registers is intrinsically Mode0, and the Mode0 reset is also used by the Master-In/Servant-Out (MISO) shift register to set up its byte to be read. A Mode0 reset is also done when calling the /SELECT routine, so the first SPI transfer in a series always starts with the MODE0 latch in reset state.

In Mode3, the clock is idle high, the down cycle shifts data onto the SPI bus, and the up cycle latches the data.

In Mode0, the clock is idle low, the initial data is already on the bus, the up cycle latches the data, and the down cycle shifts the next data onto the bus.

So to convert Mode3 to Mode0, hold the clock line down, release the hold when the Mode3 clock drops ... so the clock line is still low ... and then the Mode0 clock cycles run a half clock cycle behind the Mode3 cycle, giving 7 and a half Mode0 clock cycles. Finally, push the clock cycle back down, with provides the final half Mode0 clock cycle.

At 143 T-cycles (including JR/RET), that caps data transfer at 70KB/s on a 10MHz system, 140KB/s on a 20MHz system. If that is not fast enough, it is possible to do a similar approach that is about 1/3 faster, but it involves more selection logic, so I'm looking at this simpler approach which has all four SPI selects needed driven by a single 2->4 decoder.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~

; With the RAMdisk at I/O ports 80h-FFh, I am going to put SPI at 70h-7Fh
; Note that this is upwardly compatible with the Z180, which has internal
; I/O devices, memory mapping, and timers in 00h-3Fh

; The 2->4 decoder will be set on A0 and the /Z80_WR line.
; However, /WR=1 is not a read if /M1=0.
; So to get a true R/W selector, the select must be qualified on M1=1.
; I will also need A6, A5, and A4 high, so I'll bundle A6=1 with M1=1

NAND1_1A := /M1 ; qualify R/W=1 is actually a read
NAND1_1B := Z80_A6
NAND1_1Y =: OR2_3A ; %0[1]11.xxxx & /M1=1

; Invert A5 and A4 for the rest of the 70h-7Fh I/O range
NOT1_2A := Z80_A4
NOT1_2Y =: OR2_3B
NOT1_3A := Z80_A5
NOT1_3Y =: OR2_3C

; First generate an /IO_SELECT for 40h-7Fh
OR2_2A := A7
OR2_2B := NAND1_1Y
OR2_2C := /IOREQ
OR2_2Y =: OR2_3A ; /IO_SELECT

; Now pin down the 70h-7Fh range
OR2_3A := NOR2_2Y
OR2_3B := NOT1_2Y ; /Z80_A4
OR2_3C := NOT1_3Y ; /Z80_A5
OR2_3Y =: /DECODE1_G2 ; /SPI_IO

; Now use the Decoder for the four register states
; Write SPI_DATA: /LOAD MOSI SSR
; Read SPI_DATA: /OE MISO SSR
; Write SPI_SHIFT: generate Mode3 SPI_CLK cycle
; Read SPI_SHIFT: reset Mode0 state and load MISO output register

/DECODE1_2G := OR2_2Y
DECODE1_2A := /Z80_WR
DECODE1_2B := Z80_A0
DECODE1_2Y0 =: MOSI_SHIFT/LOAD ; /WR SPI Data
DECODE1_2Y1 =: MISO_OE ; /RD SPI DATA
DECODE1_2Y2 =: NAND1_2A =: MOSI_SCP ; SPI_CLK3
DECODE1_2Y3 =: NAND1_3A =: MISO_RCL ; /MODE0_RESET

; The Mode0 /Set /Reset latch is built from two NAND gates:
NAND1_2A := DECODE1_2Y2 ; SPI_CLK3 ; /SET
NAND1_2B := NAND2_3Y ; <= /Q
NAND1_2Y =: NAND2_3A =: NAND2_4A ; Q =>
NAND1_3A := DECODE1_2Y2 ; /RESET
NAND1_3B := NAND2_2A ; <= Q
NAND1_3Y =: NAND2_2B ; /Q =>

; When the latch is reset, Q=0, a NAND will always be 1.
; When the latch is set, Q=1, a NAND will invert the other input
; So using a NAND gate gives Mode2, the inverse of Mode0
; A NOT gate converts the Mode2 clock cycle to Mode0

NAND1_4A := NAND2_2Y ; Q output from /S /R latch
NAND1_4B := DECODE1_2Y3 ; SPI_CLK3
NAND1_4Y =: NAND2_3B =: SPI_CLK2

NOT1_6A := NAND2_3Y ; SPI_CLK2
NOT1_6Y =: SPI_CLK0

; A 74x165 Parallel In, Serial Out IC is "naturally"
; Mode0 or Mode3. The high bit is asserted on QH from
; loading, the data is latched into the low bit on the
; rising clock, at the same time that existing data is
; shifted up.

; MOSI PISO 74xx165
; VCC INH D C B A SER QH
; 16 15 14 13 12 11 10 9
; 1 2 3 4 5 6 7 8
; S/L SCP E F G H /QH GND

; MOSI POSI 74xx165
MOSI_SHIFT/LOAD := DECODE1_2Y0
MOSI_SCP := DECODE1_2Y3 ; = SPI_CLK3
MOSI_INHIBIT := SYS_GND
MOSI_QH =: SPI_MOSI
MOSI_A := Z80_D0
MOSI_B := Z80_D1
MOSI_C := Z80_D2
MOSI_D := Z80_D3
MOSI_E := Z80_D4
MOSI_F := Z80_D5
MOSI_G := Z80_D6
MOSI_H := Z80_D7

; MISO is "naturally" Mode0, latching the available
; serial data on the rising clock, shifting it on the falling
; clock.
;
; The parallel register is loaded by the (trailing) rising edge
; of the /MODE0_RESET.

; MISO SOPI 74xx595
; VCC QA SER /OE RCL SCL /SR QH'
; 16 15 14 13 12 11 10 9
; 1 2 3 4 5 6 7 8
; QB QC QD QE QF QG QH GND

/MISO_SRCLR := OR1_1Y ; clear on load MOSI
MISO_RCLK := Z80_CLK
MISO_SRCLK := NOT1_5Y ; SPI_CLK0
MISO_SER := MISO
/MISO_OE := DECODE_1Y3
MISO_QA =: D0
MISO_QB =: D1
MISO_QC =: D2
MISO_QD =: D3
MISO_QE =: D4
MISO_QF =: D5
MISO_QG =: D6
MISO_QH =: D7

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
So to tally:

Four "big" chips:
  • Z80 CPU
  • ROM: 2KB-32KB ROM
  • RAM1: 64KB SRAM system memory
  • RAM2: 512KB SRAM RAMdisk
Three SPI chips, one needed at the outset:
  • UART: eg, MAX3100
  • FlashRAM (NB: SMB/3.3v, 3.3v on breakout boards are available)
  • RTC: eg, DS1306
7 Glue Logic:
  • 1 Hex inverter (74x04)
  • 1 Quad 2-input OR (74x32)
  • 1 Triple 3-input OR (CD4075, Can be replaced by 2-input OR's if too slow)
  • 1 Quad 2-input NAND (74x00)
  • 1 Dual 2-4 Decoder (74x139)
  • 1 Parallel-In, Serial-Out SSR (74x165)
  • 1 Serial-In, Parallel-Out SSR (74x595)
BruceRMcF
Posts: 222
Joined: Sat Jan 07, 2023 10:33 pm

3 Refinements on CP/M internal Userbox card design

Post by BruceRMcF »

Every single time I post a new approach on this design, the next few days I see something.

Refinement 1: more lines from two Interrupt register latches.

First, the Interrupt latches are set up to use a two bit select, which as a side effect can leave interrupt register values of 7Fh and below or 80h and above free to be used by a Mode 2 Interrupt (selecting which one is just a matter of which pair of outputs from the 2->4 decoder are connected). However, in the design that I have, I don't actually need Mode 2 interrupt mode. I don't have anything to assert a value onto the bus for a Mode0 or Mode2 interrupt, so I have to use Mode1 interrupt, so the Interrupt registers is not actually used for interrupts at all.

So I can set up the latch selection to just use A15, which holds D7 from the Interrupt register during the /Refresh cycle, to select between the two latches.

The latch containing the RAM/ROM line needs to be in a latch with a /MasterReset, and I'm going to tend to have the 3-state 8bit latch on hand rather the always on 8bit latch that has a /Reset pin. The state of RAM/ROM has to be cleared on /Reset so that execution begins from the boot ROM. So the latch with RAM/ROM is going to remain a hex latch with a /MR input.

However, one thing that does happen on /Z80_Reset is that I and R are both cleared to 0. So if the other latch is Latch 1, selected when b7 of I is low, the /Refresh phase of the very first instruction executed by the boot ROM will write a "0" to every bit of Latch 1. That means that Latch 1 does not need a /MR input, which means that it can be an Octal latch.

With the high bit used to select the Latch, only 7bits are available in the I register. However, in the same /Refresh cycle, the high bit of the /Refresh register R is asserted on A7, and the refresh cycle increments the values in b0-b6 of R and ignores b7. So b7 can be used as an additional output line which is written to one of the bits of the octal latch whenever the high bit of I is clear.

This gives me the additional address line if switching from a DIP 512KB RAM2 to a SMB 1MB RAM2 for an A/B RAMdisk board. For programming convenience that is on Q0, latched from A7, so that the disk select bit is seperate from the SPI select bits in the I register when the high bit of I is 0, so changing the disk is a short routine that writes 00h or FFh to R then fetches the current SPI select value from it's memory location and stores it to I, ensuring that the state of b7 of R is written to Latch1.

The Heartbeat LED is moved up to b6 of the I register, with b0-b2 as the internal SPI I/O /selects and b3-b5 as external SPI I/O /selects.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Refinement two: reducing the complexity of selection logic

There is literally no I/O on this board other then the I/O on the SPI bus, so its silly to devote gates to avoiding conflict with I/O bus devices that do not exist. Since I already need an inverted /A7 line for the RAM2 chip select generation, I can use one NAND gate to NAND M1 and /A7, so that when not in interrupt mode (so M1=1), and when in the lower half of the I/O page (so A7=0 => /A7=1), a /SPI_Address is asserted, and a single OR of /SPI_Address and /Z80_IOREQ selects the 2->4 decoder that generates the byte write to the MOSI SSR, the byte read from the MISO SSR, the write to SPI_SHIFT for the clock pulse, and the read from SPI_SHIFT to reset the Mode0 low idle clock filter.

However, the routine will treat SPI_DATA as present at 7Eh and SPI_SHIFT as present at 7Fh, so that all of 00h-7Dh is available to be allocated to I/O register addresses without requiring the SPI code to be rewritten.

Also, as a result of going to only using the A15 line to select between the Interrupt Register latches, the OR of the /Refresh and /MEMREQ to generate the /Decode1_1G decoder select can be dispensed with. A15 and /Refresh can be used as the two decoder address inputs and /MEMREQ as the decoder select, with only the lines associated with /Refresh=0 connected. This is a common design pattern for a 2->4 decoder, where using one of the address input lines as a select and not connecting two of the output lines makes it a 1->2 decoder with two selects, one low, and the other either high or low.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Refinement three: cutting the glue logic count from 9 to 8

Saving several gates means that I am down to only using two NOT gates and five NAND gates.

I can save two NAND gates by switching the /S /R latch from a two-NAND /S /R gate to one half of a 74x74 general purpose dual single latch. Each side of this chip has a clocked D latch with a /clear input, /preset input, data input, clock input, latched data output, and inverse latched data output. Tying the data input and clock to ground, the /clear input can be used a /R connected to the Read SPI_SHIFT select and the /preset input used as /S connected to the Write SPI_SHIFT select, and the Q output gives the line to hold the Mode0 clock low while idle, so convert the Mode3 SPI_CLK3 to the Mode0 SPI_CLK0.

This gives three NAND gates and two NOT gates. One NOT gate can be eliminated by tying NAND1_4B to VCC so that NAND1_4Y is the inverse of NAND1_4A.

And in certain circumstances, the 74x74 latch can be used as an inverter, due to its complementary output /Q. It is a synchronous inverted latch, only inverting and then latching the value of the input at a rising clock. However, this fits I/O addressing, since in the Z80 I/O read or write cycle, the address lines are asserted in the first T-cycle, and the /IOREQ is asserted in the second T-cycle. So the second half of the 74x74 general purpose clocked latch can be used to invert A7 for selecting between RAM2 and SPI bus accesses, tying /CLR to /Z80_RESET to start in a known state, tying /PRESET to VCC, tying CL to SYS_PHI2, D to A7, and taking /Q as the /A7 line.

And with that it is down to four NAND gates and no NOT gates, trading one quad 2-input NAND and one hex inverter for one dual general purpose bit latch.
BruceRMcF
Posts: 222
Joined: Sat Jan 07, 2023 10:33 pm

Re: Retro CP/M internal Userbox card design note ...

Post by BruceRMcF »

Refinement 1:

; Interupt relies on Mode1, not Mode2.
; I.b7 = Latch2/Latch1

DECODE1_VCC := SYS_VCC
DECODE1_GND := SYS_GND
/DECODE1_1G := /Z80_MEMREQ
DECODE1_1A := /Z80_REFRESH
DECODE1_1B := Z80_A15
DECODE1_1Y0 =: /LATCH1_CLK
DECODE1_1Y1 =: DNC
DECODE1_1Y2 =: /LATCH2_CLK
DECODE1_1Y3 =: DNC

; Latch1 = 74x574
; VCC Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 CL
; 20 19 18 17 16 15 14 13 12 11
; 1 2 3 4 5 6 7 8 9 10
; /OE D0 D1 D2 D3 D4 D5 D6 D7 GND

; The first instruction executed from ROM writes 0 to Latch1,
; so no /Reset is needed, so an Octal latch may be used.
; The '574 is a tri-state latch, but output is always enabled.

; LATCH1
LATCH1_GND := SYS_GND
LATCH1_VCC := SYS_VCC
/LATCH1_OE := SYS_GND
LATCH1_CL := DECODE1_1Y0
LATCH1_D0 := Z80_A7
LATCH1_D1 := Z80_A8
LATCH1_D2 := Z80_A9
LATCH1_D3 := Z80_A10
LATCH1_D4 := Z80_A11
LATCH1_D5 := Z80_A12
LATCH1_D6 := Z80_A13
LATCH1_D7 := Z80_A14
LATCH1_Q0 =: RAM2_A19 ; reserved for A/B select
LATCH1_Q1 =: /SPI_SEL1
LATCH1_Q2 =: /SPI_SEL2
LATCH1_Q3 =: /SPI_SEL3
LATCH1_Q4 =: /EXT_SEL1
LATCH1_Q5 =: /EXT_SEL2
LATCH1_Q6 =: /EXT_SEL3
LATCH1_Q7 =: LED1

; Latch2 containing RAM/ROM requires a /RESET, available
; on the 74x174 hex latch.
; VCC Q5 D5 D4 Q4 D3 Q3 CL
; 16 15 14 13 12 11 10 9
; 1 2 3 4 5 6 7 8
; /MR Q0 D0 D1 Q1 D2 Q2 GND

; LATCH2
LATCH2_GND := SYS_GND
LATCH2_VCC := SYS_VCC
/LATCH2_MR := /Z80_RESET
LATCH2_CL := DECODE1_1Y2
LATCH2_D0 := Z80_A8
LATCH2_D1 := Z80_A9
LATCH2_D2 := Z80_A10
LATCH2_D3 := Z80_A11
LATCH2_D4 := Z80_A12
LATCH2_D5 := Z80_A13
LATCH2_Q0 =: RAM2_A14
LATCH2_Q1 =: RAM2_A15
LATCH2_Q2 =: RAM2_A16
LATCH2_Q3 =: RAM2_A17
LATCH2_Q4 =: RAM2_A18
LATCH2_Q5 =: RAM/ROM
BruceRMcF
Posts: 222
Joined: Sat Jan 07, 2023 10:33 pm

Re: Retro CP/M internal Userbox card design note ...

Post by BruceRMcF »

Refinements 2 & 3:

The first part of reduce logic gates for address decoding is bundled into the "Refinement 1"

The rest is the addressing for the SPI_DATA and SPI_SHIFT which have to work for I/O locations 7Eh and 7Fh, but there is no reason to worry about the value of A2-A6 when /IOREQ is active low ... as long as A7 is 0, and A0 selects between SPI_DATA and SPI_SHIFT, there is no need to require A2=A3=...=A6=1.

On recount, there is 1 NAND gate formally free for selecting the SPI on 00h-7Fh, so I've narrowed down one half to 40h-7Fh, so that as designed it is compatible with a Z180 ... where the Z180 has 33MHz options and a simple memory manager that provides effective support for a 128KB system RAM in a CPM Plus system, so if the Z80 daughter-board proves out, the Z180 makes a fine premium option.
Now, while it may seem like overkill to use the complementary output on a general purpose bit latch as a PHI2 synchronous inverter, that side of LATCH3 can be used as an inverter, and it can't be used to block out 00h-3Fh from the chip select for the second half of the DECODE1 part.

; Glue Logic:
; OR1 -- 1 Quad 2 input OR ; all gates used`
; NAND1 -- 1 Quad 2 input NAND ; 3 gates used
; DECODE1 -- 1 dual 2-4 decoder ; both sides used
; LATCH1 -- 1 Octal clocked latch
; LATCH2 -- 1 hex clocked latch with reset
; LATCH3 -- 1 dual clocked d-latch (74x74)
; MOSI -- 1 Parallel In, Serial out SSR
; MISO -- 1 Parallel Out, Serial in SSR

; One side of the general purpose bit latch is used to invert A7:

; Latch3 = 74x74
; VCC /2CL 2D 2CK /2PR 2Q /2Q
; 14 13 12 11 10 9 8
; 1 2 3 4 5 6 7
; /1CL 1D 1CK /1PR 1Q /1Q GND

; Using a 74x74 latch as a NOT gate`: The /A7 follows the first rising clock
; in the Z80 /IOREQ cycle:
; (1) valid address A7 (2) rising clock, /Q=/A7 (3) /IOREQ asserted

LATCH3_GND := SYS_GND
LATCH3_VCC := SYS_VCC
/LATCH3_1CL := SYS_VCC ; no clear
LATCH3_1D := Z80_A7 ; data input
LATCH3_1CK := Z80_PHI2 ; latched start of PHI2
/LATCH3_1PR := SYS_VCC ; no preset
LATCH3_1Q =: DNC
/LATCH3_1Q =: OR1_3A ; => /A7 ; available before /IOREQ low

; I/O 80h-FFh, SRAM2
OR1_3A := /LATCH3_1Q ; <= /A7
OR1_3B := /IOREQ
OR1_3Y =: /RAM2_CS

RAM2_VCC := SYS_VCC
RAM2_GND := SYS_GND
/RAM2_CS := OR1_3Y
/RAM2_OE := /Z80_RD
/RAM2_WE := /Z80_WR
RAM2_A0-A6 := Z80_A7-A12
RAM2_A7-A13 := Z80_A0-A6
RAM2_A14-A18 := LATCH2_Q0-Q4

; I/O SPI_SHIFT/DATA = 7Eh/7Fh
; Free NAND gate available to filter 00h-3Fh if Z180
NAND1_1A := /M1 ; qualify /RW=1 is actually read
NAND1_1B := /LATCH3_1Q ; /A7=1 when A7=0
NAND1_1Y =: OR1_4A ; %[0000].xxxx - %[0111].xxxx & /M1=1

NAND1_2A := /Z80_IOREQ
NAND1_2B := /Z80_A6
NAND1_2Y =: OR1_4B

OR1_4A := NAND1_1Y
OR1_4B := NAND1_2Y
OR1_4Y =: /DECODE1_G2 ; /SPI_IO

/DECODE1_2G := OR2_4Y ; select on I/O 00h-7Fh, /M1=1
DECODE1_2A := /Z80_WR ; decode RD/WR
DECODE1_2B := Z80_A0 ; decode FFh/FEh
DECODE1_2Y0 =: MOSI_SHIFT/LOAD ; /WR SPI Data
DECODE1_2Y1 =: MISO_OE ; /RD SPI DATA
DECODE1_2Y2 =: /LATCH3_2PR =: MOSI_SCP ; SPI_CLK3
DECODE1_2Y3 =: /LATCH3_2CL =: NAND1_3A =: MISO_RCL ; /MODE0_RESET

; Translate SPI_CLK3 -> SPI_CLK2 => SPI_CLK0
; 2nd half of 74x74 latch is the /S /R latch
/LATCH3_2CL := DECODE_2Y3 ; IN A,(SPI_SHIFT)
LATCH3_2D := SYS_GND ; never latching on clock
LATCH3_2CK := SYS_GND ; never latching on clock
/LATCH3_2PR := DECODE_2Y2 ; SPI_CLK3
LATCH3_2Q =: NAND2_3A ; pass through CL3 when this is high
/LATCH3_2Q =: DNC

NAND1_3A := LATCH3_2Q
NAND1_3B := DECODE1_2Y3 ; SPI_CLK3
NAND1_3Y =: NAND1_4B ; = SPI_CLK2

NAND1_4A := SYS_VCC
NAND1_4B := NAND1_3Y
NAND1_4Y =: SPI_CLK0
Post Reply