This project assumes you know about or have used SPI in some form or another. This could be with an AVR, a Raspberry Pi, or bitbanging it to a peripheral using your own 6502 design like me. This project is also focused on building a circuit for use in a very specific device: the n8 Bit Special microcomputer. This means that certain design decisions will be made to gracefully interoperate in its eventual home.

SPI

As a most basic refresher, SPI is a full-duplex serial data protocol with one orchestrator device and potentially many peripheral devices sharing some common control lines but individual device enable lines. Data is moved 8 bits at a time between orchestrator and peripheral. A typical hardware example illustrates this using 2 shift registers: one in the orchestrator and one in the peripheral, forming a circular buffer:

There are also 4 modes, each of which describes when data is sampled in which device. For details on this (and more), you can check out the SPI Wikipedia page.

Bit Banging SPI

SPI is a pretty simple protocol so its often "bit-banged": using software and some simple control lines to communicate instead of dedicated hardware. This has some disadvantages though: it's pretty slow (it's going to be significantly slower than your CPU's clock. I was able to get it to about 60khz but the CPU clock was 2Mhz!) and your CPU is dedicated entirely to the task until it's finished. It may also take a significant percentage of your computer's GPIO capability, especially with more than one device.

SPI on AVRs

AVRs have solved this by building SPI support directly into the chip itself. This hardware is exposed via 3 registers: a data register, a status register, and a control register. 

The data register is a read-write register. Writing to it initiates an SPI transfer and the data received is stored in a buffer which can be read.

The control register is a read-write register for configuring various SPI modes, roles, and speeds. It looks like this:

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
SPIESPEDORDMSTRCPOLCPHASPR1SPR0

SPR0 and SPR1 form a 2 bit clock rate divider.

CPOL and CPHA form a 2 bit mode select.

MSTR is a bit to control whether the AVR is the master or a peripheral.

DORD is a bit to specify data order (MSB vs LSB).

SPE is an SPI Enable bit.

SPIE is an SPI Interrupt Enable bit.

The status register only has 3 relevant bits, and looks like:

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
SPIFWCOLxxxxxSPI_2x

SPI_2x is a bit to double the speed (it basically forms a 3 bit clock divider)

WCOL is a read-only bit if there a write collision occurs. In other words, if data is written to the data register while a transfer was already in progress.

SPIF is a read-only bit that is set when a transfer is complete and is cleared when SPIF bit and data register are read.

The AVR also has some dedicated SPI control lines: MISO, MOSI, CLK and you can use any GPIO pins as device select lines to your peripherals.

SPI Master

I want to achieve something very similar for my microcomputer. I want dedicated hardware that appears as exposed registers that perform SPI tasks and notify me when they're done. I also want to do this in discrete logic. Could I build this in an FPGA? Sure. Can I also just use an AVR and use its dedicated SPI hardware? Sure. But I don't want to :)

So, how will I achieve this? Well, first I actually shed some of the requirements. My design will not have any mode select, it's going to be Mode 0 only. I'm also going to (for now) skip things like double speed and write collision.

Next I'm going to add to the requirements. SPI master will have hardware to control up to 4 SPI devices. The 6502 doesn't have any built-in GPIO and I don't want to dedicate VIA pins to this purpose so it makes sense to build this in.

The rest of the requirements remain: I want clock speed selection, I want interrupts, I want a set it and forget it data register that initiates transfers.

So with these requirements in mind, what will the registers look like? Let's start with the control register:

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
ITCxIENDENSEL1SEL0DIV1DIV0

DIV0 and DIV1 form a 2 bit clock divider select with the following possible values:

SPI1SPI0Clock Divide
00CLK / 2
01CLK / 4
10CLK / 8
11CLK / 16

There is no way to run this device at full CPU speed, it will always be divided in some way. Taking the default clock rate of the n8 Bit computer (~3.6Mhz) you essentially get speeds of 1.8Mhz, 900khz, 450khz, and 225khz. This is important because certain devices will not operate at too high a clock speed. SD cards, for example, require a clock speed between 100khz and 400khz to enter SPI mode.

SEL0 and SEL1 form a 2 bit device select. `00` will select device 1, `01` will select device 2, and so on.

DEN is a Device Enable bit. When on, whatever device is selected in SEL0 and SEL1 will be enabled. When off, no SPI device's CS line will be asserted.

IEN is an Interrupt Enable bit. When on, a 6502 interrupt will be asserted when a transfer has completed.

Bit 6 is unused. You could theoretically store a single bit of information there if you wanted to.

Bit 7 is an Interrupt/Transfer Complete flag. When a transfer is complete, this bit will be asserted and can be polled in a loop or tested in an interrupt handler.

The data register should behave almost exactly like the AVR SPI data register. When you write to it, a transfer will start (even if no devices are asserted). The result of that transfer will be stored and can be read.

Hardware Subsystems

To achieve this, I've split the device up into a number of subsystems. My plan is to breadboard each of these subsystems, validate them, and then integrate them at the end. I will be using a logic analyzer to view the SPI input and output to the device and comparing this to my working bitbanged solution. Lastly, I will hook my n8 Bit computer to the breadboarded design and test it out!

I have identified essentially 7 subsystems: the status register, the data register(s), the clock divider, a small state machine, device selection (and port), interrupt generation, and address decoding.

I will be posting a log entry for each of these subsystems as I tackle them, so stay tuned!