Proper SPI circuit for vintage CPUs - Page 1
Forum section: electronics / general

author: JamesIsAwkward
author_rank: Contributor
timestamp: October 07, 2024, 04:02:33 pm
content: Hey everyone!Have any of you seen a proper SPI circuit for Z80/6502/etc?Most implementations I've see just bitbang the interface but I have a couple of SPI devices I'd like to integrate with my Z80 that need to be as fast as possible.Basically from what I can tell you can either do a synchronous or an asynchronous circuit.Obviously synchronous would be the best way to do it and it would pretty much eliminate timing issues but this would mean you would likely need to drive your CPU from a very high clock that is divided down.Here's some quick napkin math for Z80 timing:My Z80 SYSCLK runs at 10 MHz.If you look at the IN/OUT timing, you have about 2ish clock cycles to shift the data in from MISO to the data bus. I know the whole IN/OUT op code takes 11 cycles total but from the time the IORQ and RD go low, you seem to only have a couple of clock cycles before the data bus is sampled.I'm using the output of a 138 decoder and ORing that with RD/WR to select my shift register and start the 8 pulses from the counting circuit.So from what I figure, at 10 MHz, a clock cycle is 100ns. So that gives me roughly 200ns to pulse the SCLK 8 times. Which would be 25ns, or 40 MHz.This means that 40 MHz would likely be the lowest speed possible to still achieve transparency from the CPU's perspective. That is, if everything else goes perfectly and there's no delays or anything then a 40 MHz shift clock should finish fast enough to be unnoticeable to the CPU when using an IN instruction. 80 MHz would be ideal because it would eliminate any possibility of a timing issue and is still cleanly dividable down to 10 MHz for the CPU.But in my case my build uses a backplane that I designed, and the beast is over 22cm long. My 10 MHz clock is on that bus and has a rise time of 5ns. I'm already hitting the limit of the bus length without some kind of termination, I have a feeling if I attempted to supply 80 MHz or even 40 MHz to the bus it would reflect like mad and I would have a bad time.So that leads me to asynchronous timing.If my math above is correct and I choose a relatively fast SCLK then I really shouldn't have to worry about timing issues right? If the circuit is able to load the shift register (plus register pin setup times and all that) between the IORQ/RD and the point that the data bus is sampled then it should Just Work™ right?Here is an example of an asynchronous SPI setup, but I would like to supply a fast enough clock (if the device supports it ofc) to not have to implement NOPs to the code to account for slow reading.https://www.ecstaticlyrics.com/electronics/SPI/fast_z80_interface.htmlI'd have to to use a modified counting circuit but the principle is the same. The IORQ/RD to the correct address will load the register and start the counter. After 8 pulses it will stop and your data should be ready.Thoughts?

author: woofy
author_rank: Frequent Contributor
timestamp: October 07, 2024, 04:57:37 pm
content: I would have thought about the fastest you could bring the data in without using dma would be with the INIR instruction. That's 21 clocks per byte or 2.1uS with a 10MHz Z80, so you don't really need to go faster than that. Your 10MHz clock will be fine for the SPI. Instead of trying to jam the SPI clocks into the read strobe, use the rising edge to trigger the next read. That way you have 2.1uS before it will be read again. This assumes you are doing sequential reads, which is likely with an SD card.

author: JamesIsAwkward
author_rank: Contributor
timestamp: October 07, 2024, 06:11:01 pm
content: That's a good point! I guess the only issue is that the first byte read will always require timing padding. Same for if you are just reading 1 byte at a time here and there instead of bursting.In my case I won't likely implement an SD card (never say never) but I do want to try out this wiznet 5500 SPI module I found. I suppose most of the time I will be reading in bursts with it too... hmm..I don't know, it just sounds nice having a circuit that would work fast enough for the CPU to just use IN/OUT normally no matter the situation.EDIT: I know back in the day slow I/O would just use WAIT which I could also easily implement in this circuit.For my build in particular this wouldn't work though because I will be multi-threading and not using those types of signals.I suppose I can just implement a status register, and poll it to see if the SPI counting circuit is still counting (if Q' on the counting circuit SR latch is still HIGH aka it still hasn't counted to 8 and the data isn't ready).

author: Benta
author_rank: Super Contributor
timestamp: October 07, 2024, 09:02:47 pm
content: Wouldn't the Z80 SIO do this for you?

author: Ian.M
author_rank: Super Contributor
timestamp: October 07, 2024, 09:46:51 pm
content: Quote from: Benta on October 07, 2024, 09:02:47 pmWith a lot of faffing around and extra glue logic! e.g. it starts accepting bits on the rising edge of /RxC that *precedes* the falling edge of /SYNC, and the bit order is reversed.A better choice might be to use a slave MCU as a serial bus I/O controller. I'd select a Microchip PIC with their PSP feature - Parallel Slave Port, which is an 8 bit CPU bus interface, with a /CS input signal, data to the PIC latched by a /WR strobe and data from the PIC gated onto the bus by a /RD signal, then write PIC code to use its MSSP (Master Synchronous Serial Port) for SPI to *whatever* slave devices you want, double-buffering all transfers in PIC RAM, and implementing one or more control and status registers. You'll need to latch a few bits of the address bus when PSP /CS goes low, and feed them to another PIC port so the PIC can distinguish between data and control transfers (there's not much time in a 10MHz Z80 I/O read or write cycle for the PIC to read the address bits if you don't latch them).PSP is only found on 40 or higher pin count devices, as it uses port D for the bus and port E for the control signals. You'll also want one of the old 5V capable parts.The MSSP can also do I2C, (but sharing pins with SPI, so you'd need a pair of P-MOSFETs to isolate the I2C bus while doing SPI) and a PIC18 with PSP typically also have a fancy UART, ADC inputs, PWM outputs and a bunch of timers so it could handle multiple interfacing needs with a low chip count.If you don't fancy PIC assembler the free mode of their XC8 C compiler is entirely adequate for most projects, and even legal for commercial use if you can live with no (official) support and considerable code bloat compared to Pro mode.The Microchip product selector:https://www.microchip.com/maps/microcontroller.aspxwill let you find a PIC with PSP capable of 5V operation - in 'Voltage' select an 'Operation Voltage' range of 4 to 5.5 to eliminate 3.3V (or lower) parts, and scroll down to 'Digital' and select PSP in the 'Parallel Port' box to get the CPU bus interface you need.

author: Andy Chee
author_rank: Super Contributor
timestamp: October 07, 2024, 11:18:12 pm
content: Quote from: Ian.M on October 07, 2024, 09:46:51 pmIf the OP wants to remain in the same retro MCU family, what about using a second Z80/6502 and programming it as an SPI peripheral?Of course, board size might be an issue if you need a second ROM for this second processor.

author: JamesIsAwkward
author_rank: Contributor
timestamp: October 08, 2024, 01:20:14 pm
content: Using an MCU like a PIC is a great idea but I'm trying to use something like that as last resort!Keeping it retro is my main goal. I'm going to need a few of these SPI circuits so using a second processor is also out, though I think this is a really cool idea!(I'm considering using a second Z80 or maybe a 6502 to run my sound card though!)I'm attempting to build something with 74xx ICs. I do have some GALs I could use but I'm trying to keep those off limits as well.

author: Ian.M
author_rank: Super Contributor
timestamp: October 08, 2024, 02:10:12 pm
content: In that case you probably need some FIFOs to decouple the SPI side data rate from the Z80 IO bus speed. 74xx... 16x4 FIFOs used to be readily available. Most of the classic ones have been discontinued so you'll be scraping around for NOS stock.I'd put an 8 bit wide (2x 4bit ICs) 16 word deep FIFO in each direction between the SPI SerDes and the Z80 DATA bus. Preload up to 16 bytes to output and write to the port controlling the SerDES and your /SS lines to start the transfer. You've then got up to 256 SPI clock cycles to handle incoming data queuing in the RX FIFO and/or stuff more data in the TX FIFO Have flags for whether or not the TX FIFO going empty or the RX FIFO going full should suspend SCLK till the Z80 can catch up, as some transfers will be write only or read only, and you don't need the overhead of stuffing dummy bytes to send or discarding unwanted received bytes. For a write only or read only (except for up to 16 header bytes) transfers and a 10MHz Z80 that should allow continuous transfers at up to 3.8MHz SPI clock using OTIR/INIR or OTDR/INDR or if you care to throw program memory at the problem with a block of 16 OUTI/OUTD or INI/IND instructions and a JR NZ back to the top of the block, you should be able to sustain 4.77 MHz continuous transfers.Your next problem will be developing a programmable clock generator for the SerDes.Incidentally, why would you need a few of these SPI interfaces? The Z80 will be hard put to service one at typical SPI clock speeds, and you can simply use a couple of 74xx138 3 to 8 line decoders wired as a 4 to 16 line decoder, and a 4 bit latch to control 15 /SS outputs for 15 SPI devices (you loose one to needing an all inactive state). Gate the remaining '138 enables inactive during writes to the control port so you don't get /SS glitches when switching to a different slave.Incidentally, using a PIC MCU is *almost* retro - General Instruments developed the PIC1640 as a Peripheral Interface Controller for theirCP160016 bit microprocessor back in 1977, but the CP1600 was somewhat of a flop due to G.I.s marketing policy of only talking to *BIG* customers, and lack of second sourcing, so the standalone PIC1650 MCU was more popular and is the grand-daddy of all the 8 bit PICs Microchip developed after they were spun off from G.I. in 1987. There's a mid-1997 windowed CERDIP PIC16C74A with PSP on Ebay right now:https://www.ebay.co.uk/itm/186711079233,but that's a high cost for retro as its only got 4K word program memory, 192 bytes RAM and a 20MHz (5 MIPS) max clock speed + you need a compatible EPROM programmer and UV eraser to develop with it. Heck I wouldn't even use the newer FLASH version (PIC16F877A) on a bet due to its horrible architecture vs PIC18 and ridiculous price/performance ratio (though it would be your best bet for doing the development then back-port to the PIC16C74A).Incidentally Western Design Center, Inc. do some 6502 core MCUs e.g.https://www.westerndesigncenter.com/wdc/w65c265s-chip.phpwhich is actually in stock at Mouser, so maybe a 6502 to run your periperals, not just your sound card is not so crazy?

author: JamesIsAwkward
author_rank: Contributor
timestamp: October 08, 2024, 02:16:03 pm
content: For my personal design I'm doing a backplane with daughter cards, so each SPI device will likely have its own card. But I haven't set my plan in stone yet!Also my real time clock circuit will set on the CPU card, which needs its own SPI circuit as well, though it has a 2 MHz SCLK limit so these "fast" circuit ideas don't really apply thereI think a part of the whole idea is that when you search for SPI circuits for vintage CPUs you never really find a definitive circuit. There's no "standard", it will be cool if our community at least had a starting place that was also vintage-ish as possible!

author: Ian.M
author_rank: Super Contributor
timestamp: October 08, 2024, 02:53:48 pm
content: Four SPI lines on your backplane and select which device by clocking out a byte with /SS high (which goes into a SIPO feeding a magnitude comparator vs some DIP switches or links, on each card to gate /SS to the actual SPI devices on that card) and you can support up to 255 SPI devices system wide from one SPI SerDes 'engine'.

author: Andy Chee
author_rank: Super Contributor
timestamp: October 08, 2024, 03:14:26 pm
content: How about using something from the 68HC11 family? Onboard SPI peripheral comes standard, some versions have onboard program EEPROM (thus negating the need for an external ROM).Its retro pedigree is rooted with the 6800 processor, which we know Chuck Peddle left Motorola to found his MOS Technology and design the 6502.

author: JamesIsAwkward
author_rank: Contributor
timestamp: October 22, 2024, 05:06:49 pm
content: Quote from: Andy Chee on October 08, 2024, 03:14:26 pmI actually think my next vintage CPU project will be on a M68k or something similar!Well small update, I've looked into a few other possibilities but this is a headache. Me wanting this to run "full speed" on the first byte, coupled with my desire to stay as "vintage" as possible even if that just means using DIP versions of modern tech, means I'm stuck at the moment lolI've looked into PLL IC's, but the only THT versions I can find can only do 38 MHz output max, and I'm going to need 45-50MHz to do this fast enough to fit my vision.I've looked into running the shift registers directly from a 50 MHz clock but I ran into issues trying to turn the clock signal on and off to the registers. Without a '163 counter doing the work and making it all synchronous means I have to find a way to isolate the shift registers from the clock when not in use... but do that in a synchronous way, meaning after I enable the clock signal I would have to somehow only allow the first pulse to happen on the rising edge, just in case I enabled it while it was already HIGH. If any of this makes sense.Either way I'm still pondering!I understand there are easier ways, like using MCU and PICs, but brute forcing this seems more fun and more in the vintage spirit!

author: JamesIsAwkward
author_rank: Contributor
timestamp: November 19, 2024, 07:27:07 pm
content: Alright I've finally succumbed to reason... I'm going to just do a synchronous setup and use a '163 to divide the signal by 2 (or more if needed for slower SPI devices).I attempted to try to do a "full speed" circuit by using SYSCLOCK as the SCLK and gating it in front of the shift registers using a '125. I would only allow the clock signal to pass when the SPI run latch was set.But I had a lot of issues and I think you guys are right... I probably don't need to worry about it running as fast as possible. I've wasted a lot of time min-maxing this when I probably don't need to...I've attached my current circuit if anyone wants to look at it and sanity check me. I haven't had a chance to breadboard the circuit yet unfortunately.What I would like to do next is modify this circuit in a way that will allow me to user INIR and other related instructions.I'm thinking of adding a better control/status register setup and then maybe I could enable a "burst mode" that would cause the circuit to grab a byte in advance after every read like woofy suggested. In code it wouldn't be a big deal, I would just request the first byte, set the control to burst mode, poll until the first byte is ready, then INIR or whatever like normal. I would just have to make sure to do the INIR loop 1 less time to account for the first byte being manually gathered.Also a quick note, I'm using parts I have on hand when I can, thus why I have a '138 being mostly wasted.I would love to hear your thoughts if you have time!

author: JamesIsAwkward
author_rank: Contributor
timestamp: November 20, 2024, 05:48:05 pm
content: Never mind that attachment, I found some flaws after I simulated it with Digital. I need to make some tweaks!

author: JamesIsAwkward
author_rank: Contributor
timestamp: December 11, 2024, 04:02:11 pm
content: Alright here is my final SPI circuit!To read a byte you have a few options.You can send a READ command to the SPI_CLOCK_START address, which will cause the SCLK to pulse 8 times and load the incoming byte into the MISO register.You can send a WRITE command to the SPI_WR address, (and hope that doesn't mess up the data transfer process for some reason) which does the same as aboveor you can enable AUTOLOAD which will pulse the SCLK 8 times after each READ, which will allow you to burst read N number of bytes with less overhead. Your first byte will be junk but after that you're good to goI had a variant with a "control" register but my use case is very specific and it was adding complexity without much benefit. It would be easy to add one with a programmable counter or something to get dynamic SCLK speeds. Also if you wanted to do a proper SPI chain you'd need to add some more CS selects for multiple devices in the chain. I only needed 1 so that's all I added.I have one other SPI device that needed a slower SCLK. In that case I just added a 74xx163 behind the first one, piped SYSCLK to its CP pin, and then connected the output of that counter into the CP pin of the main one. This way I can go anywhere from 1/2 SYSCLK speed down to 1/32 speed depending on the output pin of the new counter.I would like to find a way to reduce the gate count a bit but I'm not sure if that's possible.Anyway, feel free to use this or adapt it to your own projects. I have tested this on my breadboard and it seemed to work without any issues but you may need to test it yourself first!Thanks to everyone who gave me help and guidance. I'm glad I went with a synchronous circuit.