Skip to content

VHDL · Chapter 20.2 · Capstone Projects

Capstone: SPI Controller

The second capstone is an SPI master, which adds configurable clock polarity and phase to the serial-transceiver skills from the UART. SPI is full-duplex, so every clock pulse simultaneously shifts a bit out on MOSI and samples a bit in on MISO, letting a single shift register exchange a word in each direction at once, most-significant-bit first by convention. The master generates its serial clock from a clock-enable tick rather than a divided clock, frames each transfer by asserting chip-select, and counts bits with a small state machine. The subtle part is the four modes set by clock polarity and clock phase, which decide the idle clock level and which edge shifts versus samples. Getting the shift edge and sample edge right for the selected mode is the whole correctness question. Generics make it reusable, and verification runs across all four modes and chip-select timing.

Intermediate16 min readVHDLCapstoneSPICPOL/CPHAFull-DuplexSerial

1. Engineering intuition — one clock, two directions, four phase choices

SPI is simpler than UART in one way — there is a shared clock (SCLK), so the receiver does not have to recover timing — and more configurable in another: the master and slave must agree on when data is driven and when it is sampled relative to SCLK. That agreement is the CPOL/CPHA mode. Picture SCLK as a metronome: on one edge each side changes its output bit; on the other edge each side reads the incoming bit. Which edge does which, and whether SCLK idles high or low, are the four modes. Because the link is full-duplex, the same shift register both pushes a bit out on MOSI and pulls a bit in on MISO every beat — send and receive are the same operation. Hold that picture — a shift register clocked by SCLK, with the shift edge and sample edge set by the mode — and the design follows.

2. Formal explanation — the SPI master architecture

spi_architecture.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- GENERICS: rate + word width + mode select.
entity spi_master is
    generic ( CLK_FREQ  : positive := 50_000_000;
              SCLK_FREQ : positive := 1_000_000;
              WIDTH     : positive := 8;
              CPOL      : std_logic := '0';    -- idle clock level
              CPHA      : std_logic := '0' );  -- sample on first or second edge
    port ( clk, rst : in  std_logic;
           start    : in  std_logic;  busy : out std_logic;
           tx_data  : in  std_logic_vector(WIDTH-1 downto 0);
           rx_data  : out std_logic_vector(WIDTH-1 downto 0);
           sclk, mosi, cs_n : out std_logic;   -- to the slave
           miso     : in  std_logic );          -- from the slave
end entity;
 
-- SCLK from a clock-ENABLE tick (NOT a divided clock, 17.4). Idle level = CPOL.
-- CPOL/CPHA -> which SCLK edge SHIFTS (drives MOSI) and which SAMPLES (reads MISO):
--   CPHA=0: sample on the FIRST edge, shift on the second.   CPHA=1: shift first, sample second.
--   CPOL sets the idle level (and thus whether 'first edge' is rising or falling).
-- FSM: IDLE -> assert CS_n='0' -> run WIDTH SCLK pulses (shift out MOSI / sample in MISO) -> deassert CS.

The SPI master is generics + an SCLK tick (clock enable) + a shift register (MOSI out, MISO in, simultaneously) + CS framing + a bit-counter FSM, with CPOL/CPHA deciding the shift edge and sample edge. Full-duplex means one shift operation serves both directions.

3. Production usage — full-duplex shift on the mode's edges

spi_transfer.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- One transfer: assert CS, then for each of WIDTH SCLK pulses, SHIFT out on one edge, SAMPLE in on the other.
process (clk) begin
    if rising_edge(clk) then
        if rst = '1' then state <= IDLE; cs_n <= '1'; sclk <= CPOL; busy <= '0';
        elsif sclk_tick = '1' then
            case state is
                when IDLE =>
                    sclk <= CPOL; cs_n <= '1';
                    if start = '1' then shreg <= tx_data; bitc <= 0; cs_n <= '0'; busy <= '1'; state <= XFER; end if;
                when XFER =>
                    sclk <= not sclk;                          -- toggle SCLK each tick
                    -- on the SHIFT edge: drive mosi <= shreg(WIDTH-1); shift shreg left.
                    -- on the SAMPLE edge: shreg(0) <= miso; (full-duplex: out and in on the same word)
                    if bitc = WIDTH-1 and sample_edge then state <= DONE; else bitc <= bitc + 1; end if;
                when DONE =>
                    cs_n <= '1'; sclk <= CPOL; rx_data <= shreg; busy <= '0'; state <= IDLE;
            end case;
        end if;
    end if;
end process;
-- 'sample_edge' / 'shift_edge' are derived from CPOL and CPHA -> selects which SCLK transition does which.

What hardware does this become? A serial master: an SCLK generator (toggled on the tick, idling at CPOL), one shift register that drives MOSI from its top bit and captures MISO into its bottom bit, a CS output framing the word, and a bit-counter FSM. The decisive logic is the CPOL/CPHA-derived edge selection: which SCLK transition is the shift edge (change MOSI) and which is the sample edge (read MISO). Because the exchange is full-duplex, the same register both transmits tx_data and assembles rx_data — there is no separate receive path. Generics make one source serve any clock ratio, word width, and mode, exactly like the UART.

4. Structural interpretation — the SPI master

SPI master: SCLK generator, full-duplex shift register on MOSI/MISO, chip-select, and a bit-counter FSMclocksequencewhich edgeSCLK generatortick toggles SCLK, idle =CPOLbit-counter FSMCS frame + WIDTH pulsesshift register(full-duplex)MOSI out / MISO in,MSB-firstCPOL/CPHA edge selectshift edge vs sample edge12
An SPI master generates SCLK from a clock-enable tick (idling at the CPOL level) and runs a full-duplex shift register clocked by it. Chip-select frames each transfer. On the shift edge selected by CPOL and CPHA, the shift register drives its top bit onto MOSI; on the opposite (sample) edge it captures MISO into its bottom bit, so one register both transmits tx_data and assembles rx_data simultaneously. A bit-counter FSM sequences chip-select assertion, the WIDTH clock pulses, and deassertion. Generics set the clock ratio, word width, and CPOL/CPHA mode. This is the integrated SPI structure; the waveform below shows a mode-0 transfer with MOSI changing on one edge and MISO sampled on the other.

5. Simulation interpretation — a mode-0 SPI transfer

SPI mode 0 (CPOL=0, CPHA=0): MOSI changes on falling SCLK, MISO sampled on rising

8 cycles
SPI mode 0 (CPOL=0, CPHA=0): MOSI changes on falling SCLK, MISO sampled on risingCS asserted (low); SCLK starts. CPHA=0: MISO sampled on the RISING edgeCS asserted (low); SCL…MOSI changes on the FALLING edge (the shift edge for mode 0)MOSI changes on the FA…after WIDTH pulses, CS deasserts; rx_data holds the assembled MISO wordafter WIDTH pulses, CS…cs_n10000001sclkmosi0M7M7M6M6M5M5M5miso0S7S7S6S6S5S5S5t0t1t2t3t4t5t6t7
In mode 0 the master samples MISO on the rising SCLK edge and changes MOSI on the falling edge, so data is stable when each side reads it. Chip-select frames the transfer, and after WIDTH pulses the shift register holds the received word while tx_data has been driven out — full-duplex in one pass. Selecting the correct shift and sample edges from CPOL/CPHA is exactly what makes the link interoperate with the slave.

6. Debugging example — wrong CPOL/CPHA edge (and a missed full-duplex capture)

Expected: correct data exchange with the slave. Observed: the master reads garbage on MISO (or the slave misreads MOSI), often off by one bit or completely scrambled, and it works with one slave but not another. Root cause: the shift and sample edges did not match the selected CPOL/CPHA mode — the master sampled MISO on the wrong SCLK edge (when the data was still changing) or shifted MOSI on the sampling edge, so each side read the other mid-transition; or the design treated SPI as half-duplex and failed to capture MISO while transmitting. Fix: derive the sample edge and shift edge strictly from CPOL and CPHA (sample when data is stable, shift on the opposite edge), and capture MISO into the same shift register on the sample edge so the transfer is truly full-duplex. Engineering takeaway: SPI correctness is entirely about matching the shift vs sample edge to the CPOL/CPHA mode and capturing MISO simultaneously with driving MOSI — a wrong edge or a missed capture scrambles the data and breaks interoperability.

spi_edges_from_mode.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: sample MISO on the same edge MOSI changes -> read mid-transition -> garbage.
-- FIX: sample on the STABLE edge, shift on the opposite, both derived from CPOL/CPHA; capture MISO full-duplex.
-- if sample_edge then shreg(0) <= miso; end if;     -- read when stable
-- if shift_edge  then mosi <= shreg(WIDTH-1); shreg <= shreg(WIDTH-2 downto 0) & '0'; end if;

7. Common mistakes & what to watch for

  • Wrong shift/sample edge for the mode. Derive both strictly from CPOL/CPHA; sample when data is stable, shift on the opposite edge — a wrong edge scrambles data.
  • Treating SPI as half-duplex. Capture MISO into the shift register on the sample edge while driving MOSI — every beat exchanges both directions.
  • Dividing the clock for SCLK. Generate SCLK from a clock-enable tick idling at CPOL, not a fabric-divided clock (17.4).
  • CS timing errors. Assert CS before the first edge and hold it through the whole word; deassert only after the last bit (some slaves need setup/hold margins).
  • Skipping the mode matrix in verification. Test all four CPOL/CPHA modes, CS framing, and back-to-back transfers, not just mode 0.

8. Engineering insight & continuity

The SPI capstone adds configurable phase/polarity to serial design: an SCLK tick (clock enable, idling at CPOL), a full-duplex shift register exchanging MOSI and MISO every beat, CS framing, a bit-counter FSM, and — the crux — CPOL/CPHA-derived shift and sample edges, all verified across the four-mode matrix. It is the model for any clocked serial master. The next capstone returns to a pure datapath/control storage block built for robustness — the next lesson, Capstone: Parameterized FIFO, a fully generic, verified queue.