SPI-04: SPI Transactions
Building programmed I/O on top of SPI β how multiple exchanges combine into read, write, and multi-byte streaming transactions. Header format, unused wires, burst accesses, and performance implications.
πExchange vs Transaction
SPI-03 covered the fundamental unit of SPI communication β the exchange: exactly 8 bits transferred simultaneously in both directions. But 8 bits alone are rarely enough for real-world communication. A CPU talking to a peripheral needs to say what address to read or write, whether it is reading or writing, and what data to transfer. This requires building a higher-level structure on top of exchanges.
That structure is called an SPI transaction. The key distinction:
| Term | Definition | Duration |
|---|---|---|
| Exchange | Transfer of exactly 8 bits between master and slave β simultaneously in both directions | 8 SCLK cycles, SS stays LOW throughout |
| Transaction | One or more consecutive exchanges that together implement a complete read or write operation at a specific address | 16+ SCLK cycles (2+ exchanges), SS stays LOW across all exchanges |
In the programmed I/O model that SPI transactions implement, the CPU (master) communicates with peripherals (slaves) through register addresses. A write transaction sets a register value; a read transaction fetches one. This is the same model used for memory-mapped I/O in full CPU architectures β SPI simply serialises those operations over 4 wires.
πThe Transaction Header Format
Every transaction begins with a header exchange β the first 8-bit SPI exchange in which the master tells the slave:
- Which register address to access
- Whether the operation is a read or write
- Whether this is a single-byte or multi-byte (streaming) transaction
First exchange β Header byte structure (MOSI)
6-bit address (bits 7β2) Β· Read/Write flag (bit 1): 1 = write, 0 = read Β· Stream flag (bit 0): 1 = multi-byte, 0 = single byte
Header byte transmitted by master on MOSI during the first exchange. The 6-bit address field selects the register; R/WΜ (bit 1) specifies the direction; STREAM (bit 0) flags a multi-byte operation. MISO is all zeros during this exchange β the slave has nothing to send yet.
Header byte seen on MOSI: 0x59 = 0101 1001β
Field extraction:
Bits 7β2 = 010110 = address 0x16 (22 decimal)
Bit 1 = 0 β Read operation
Bit 0 = 1 β Streaming (multi-byte)
Interpretation: Master wants to read from register address 0x16, and this is a streaming transaction β more exchanges will follow specifying the length and delivering the data.
This is exactly Transaction 1 from the textbook’s streaming exercise (page 17 of the PDF).
π₯Read Transaction
A single-byte read transaction uses exactly two consecutive exchanges with SS held LOW throughout:
- Exchange 1 (Header): Master sends header byte on MOSI (address + R/WΜ=0 + STREAM=0). Slave sends zeros on MISO β it has not yet fetched the data.
- Exchange 2 (Data): Master sends zeros on MOSI (nothing to say). Slave sends the requested register data on MISO.
Single-byte read transaction. Exchange 1: master sends header 0x59 (addr=0x16, read, single). Exchange 2: master sends 0x00; slave returns 0xA5 from the requested register. SS stays LOW across both exchanges.
π€Write Transaction
A single-byte write transaction also uses two consecutive exchanges, but the roles of MOSI and MISO in exchange 2 are reversed:
- Exchange 1 (Header): Master sends header byte with R/WΜ=1 (write). Slave sends zeros.
- Exchange 2 (Data): Master sends the data byte to write on MOSI. MISO is idle (zeros) throughout β there is nothing for the slave to send back.
- Header: R/WΜ = 0
- Exchange 2 MOSI: zeros (unused)
- Exchange 2 MISO: register data from slave
- MOSI unused for one exchange
- MISO unused for one exchange
- Header: R/WΜ = 1
- Exchange 2 MOSI: data to write to slave
- Exchange 2 MISO: zeros (idle throughout)
- MOSI active both exchanges
- MISO unused entirely
Single-byte write transaction. Exchange 1: master sends header 0xF5 (addr=0x3D, write, stream). Exchange 2: master sends data 0xB4 to be written; MISO stays idle (all zeros) for the entire transaction β there is no acknowledgement in SPI.
πUnused Wires & Don’t-Care Bits
A key feature β and potential confusion point β of SPI transactions is that not both data wires are used in every exchange:
| Transaction type | Exchange 1 (header) | Exchange 2 (data) |
|---|---|---|
| Read | MOSI active (header) MISO = zeros (slave has no data yet) | MISO active (read data) MOSI = zeros (master has nothing to say) |
| Write | MOSI active (header) MISO = zeros | MOSI active (write data) MISO = zeros (idle throughout) |
β‘Performance Implications
Every 2-exchange transaction spends the entire first exchange transmitting address/control information β no data is moved during that time. For single-byte operations this is acceptable, but when reading or writing many consecutive bytes, the overhead is significant.
Efficiency of 2-exchange transactions:
- Total exchanges: 2 per byte transferred
- Useful data exchanges: 1 (50% efficiency)
- For 16 bytes: 32 exchanges, 16 headers = 50% channel time wasted on headers
This is why peripherals that transfer blocks of data (accelerometer FIFO, display buffers, flash memory) all support multi-byte (burst/streaming) transactions.
πMulti-Byte Streaming Transactions
A streaming transaction allows the master to transfer multiple bytes in a single transaction by keeping SS LOW across multiple data exchanges. Two challenges must be solved:
Challenge 1 β Signalling a multi-byte operation
A reserved bit in the header flags the transaction as streaming. In the format we are using, bit 0 (STREAM) = 1 indicates a multi-byte transaction.
Challenge 2 β Specifying the byte count
Two common approaches:
| Method | How it works | Trade-off |
|---|---|---|
| SS duration | Number of data bytes = (total SS-low cycles β 8) / 8. The master simply keeps SS LOW for as many extra cycles as bytes it wants. | Simple, no extra exchange needed. Requires master to know the count before starting. |
| Length exchange | A second header exchange sends the byte count N. Then N data exchanges follow. | More flexible β slave learns count before data starts. Costs one extra exchange per transaction. |
Exchange 1: Header (addr + R/W + STREAM=1)
Exchange 2: Length N
Exchanges 3 through N+2: Data bytes (read on MISO or write on MOSI)
π₯π₯Multi-Byte Read Transaction
Streaming read transaction: 5 exchanges total. Exchange 1 sends header (0x59 = addr 0x16, read, stream). Exchange 2 sends length (N=3). Exchanges 3β5: master sends zeros, slave returns three data bytes 0xA5, 0xBF, 0x4D on MISO. Output log: RD STREAM 16 a5 bf 4d.
π€π€Multi-Byte Write Transaction
Streaming write transaction (SS-duration method). Exchange 1: header 0xF5 (addr=0x3D, write, stream). Exchanges 2β4: three data bytes 0x12, 0x34, 0x56 sent on MOSI. MISO stays idle throughout. Output log: WR STREAM 3d 12 34 56.
Given: CPOL=0, CPHA=1, MSB-first. You observe the following sequence of exchange values (from a logic analyser):
| Exchange | MOSI (masterβslave) | MISO (slaveβmaster) |
|---|---|---|
| 1 | 0x59 | 0x00 |
| 2 | 0x03 | 0x00 |
| 3 | 0x00 | 0xA5 |
| 4 | 0x00 | 0xBF |
| 5 | 0x00 | 0x4D |
Step 1 β Decode header (exchange 1, MOSI=0x59=0101 1001):
Bits 7β2 = 010110 = 0x16 (address 22)
Bit 1 = 0 β Read
Bit 0 = 1 β Streaming
Step 2 β Read length (exchange 2, MOSI=0x03): N = 3 data bytes follow.
Step 3 β Collect data (exchanges 3β5, MISO): 0xA5, 0xBF, 0x4D
Output: RD STREAM 16 a5 bf 4d
This is exactly Transaction 1 from the textbook’s part-2 streaming exercise on page 17.
π¬VLSI Connections
In a real SoC, every peripheral (GPIO controller, UART, SPI itself, timers) is memory-mapped at a base address. When a CPU accesses address 0x4000_2000 to configure a GPIO pin, the underlying bus transaction β whether AXI, APB, or something else β carries exactly the same information as an SPI transaction: address, R/W direction, and data. SPI is simply a serialised, 4-wire version of the same programmed I/O model that runs on a parallel bus inside the chip. Understanding SPI transactions gives you a concrete mental model for how CPU-peripheral communication works at every level of abstraction.
Multi-byte streaming transactions map directly to FIFO-based peripheral design. An accelerometer (e.g. LIS3DH) stores X, Y, Z samples in an internal FIFO. A burst read fetches all available samples in a single transaction β one header exchange, then N data exchanges with SS held low. The N is bounded by FIFO depth. In RTL design, you must size the FIFO so it does not overflow between SPI transactions β this is a classic SPI-to-system-clock domain design problem involving asynchronous FIFOs (which you studied in the DE sequential circuits articles).
The transaction decoder you are about to implement in SPI-06 is structurally identical to a SystemVerilog BFM (Bus Functional Model) used in RTL verification. A BFM watches SCLK, SS, MOSI, and MISO and reconstructs transactions for the scoreboard to compare against expected values. Writing the software decoder in SPI-06 gives you exactly the logic you would later implement as an SV class with a run() task that loops reading the clocking block inputs. The only difference is the language and the scheduling model.
