Partition | Owns | Does not own |
|---|---|---|
Bus adapter | Handshake, response, transport errors | Field side effects |
CSR bank | Address decode, access types, reset values | AXI/APB timing |
Control FSM | Start/stop, sequencing, timeout | Software bus state |
Datapath/FIFO | Serialization, buffering, counters | Interrupt policy |
IO wrapper | Synchronizers, pin enables, pad-facing timing | Register map |
Read the picture from the top: each arrow label is the only thing that crosses that boundary.
Accepted operations only. The CSR bank turns a bus request into start_pulse or tx_fifo_push after its reject checks; a rejected request crosses nothing.
Two sides of each FIFO. Software or DMA pushes the TX FIFO; the engine pops it. The two must never be confused.
Pins live at the bottom. Synchronisers and output enables sit in the IO wrapper, away from the register map.
The CSR boundary enqueues TX words; the SPI engine dequeues them when it can serialize and enqueues RX samples. engine_busy is the single busy owner (including start reservation on the acceptance edge); no separate stale busy_q gates commands. Software and DMA arbitrate for each FIFO port before acceptance. The engine never sees AXI-Lite or APB signals.
Request at internal req_take | CSR response | Side effect owner |
|---|---|---|
TX write while tx_fifo_full | Error, no enqueue; software retains/retries word | CSR boundary |
TX write with space and port grant | OK | One tx_fifo_push |
RX read while rx_fifo_empty | Error, no dequeue | CSR boundary |
START while engine_busy or invalid config | Error plus sticky illegal-command event | CSR boundary; engine unchanged |
Legal START | OK | One start_pulse; engine reserves busy immediately |
Use the linked CSR single-outstanding contract: acceptance is req_valid && req_ready at an edge; full/empty/busy are sampled there. csr_start_write/csr_tx_write/csr_rx_read below are decoded committed one-cycle req_take pulses, even for a request returning error. Error responses are retained until consumed, and no invalid operation is silently reported as success. RX return data additionally needs the FWFT or registered-result adapter described below.
// snippet: clean internal contracts
assign start_reject = csr_start_write && (engine_busy || !config_valid);
assign tx_reject = csr_tx_write && tx_fifo_full;
assign rx_reject = csr_rx_read && rx_fifo_empty;
assign csr_op_error = start_reject | tx_reject | rx_reject;
assign start_pulse = csr_start_write && !start_reject;
assign tx_fifo_push = csr_tx_write && !tx_reject;
assign rx_fifo_pop = csr_rx_read && !rx_reject;
// TX engine dequeue is distinct from software/DMA enqueue.
assign tx_fifo_pop = engine_tx_take;
assign busy_o = engine_busy;
assign done_event = engine_done;
assign error_event = timeout_error | protocol_error | start_reject;Illustrative edge | Bus/internal ownership | Result |
|---|---|---|
E0 | TX write accepted internally while full | Error response saved; zero pushes |
E1/E2 | APB ACCESS held or AXI response stalled | Same error held; no replay even if engine frees space |
E3 | Response consumed | One rejected operation completes |
E4 | Software retry accepted with space | Exactly one enqueue (not engine dequeue) |
E5 | START accepted while engine_busy=1 | One error + illegal-command cause; zero start pulses |
The internal interface names accepted FIFO operations and start events instead of leaking bus channel levels into the engine.
At the CSR/peripheral contract boundary, with a documented stall or error policy. The protocol engine should only see accepted FIFO pushes.