Skip to content
VLSI Mentor

SPI · Module 14

Synthesizable Slave Architecture and RTL Review

Nine blocks assembled and wired to the master of Chapter 13.11 byte for byte, with nothing in between. They interoperate on the first attempt — and then integration finds two things neither module's own bench could: a truncated last half-period at every divisor, and a master's MOSI arriving one cycle after the edge that launched it.

Nine blocks exist. Each was verified against pin-level requirements rather than against the others, which was deliberate and is now the thing being tested:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   14.1  spi_slave_frontend    synchronise, recover edges, measure the ratio
   14.8  spi_slave_safe_idle   refuse a transaction begun during reset
   14.2  spi_slave_cs          the transaction boundary, and its classification
   14.6  spi_slave_mode        capture and launch from CPHA alone, plus diagnosis
   14.3  spi_slave_rx          capture MOSI, assemble words
   14.7  spi_slave_frame       complete words and partials, kept apart
   14.4  spi_slave_tx          launch MISO, including the awkward first bit
   14.5  spi_slave_oe          drive only while selected, release in time
   14.9  spi_slave_regs        the system interface, with a default and a seqlock

The master in this chapter's testbench is not a model. It is spi_master_top from Chapter 13.11, byte for byte, driven through its own register interface exactly as software would drive it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   master.sclk  -> slave.sclk_pin
   master.mosi  -> slave.mosi_pin
   master.cs_n0 -> slave.cs_n_pin
   slave.miso   -> master.miso

Neither design has been adjusted to suit the other. They interoperate on the first attempt.

Two designs, built and verified independently against pin-level requirements rather than against each other. What does integration still find?

Two things — and both of them are things that only integration could find.

1. The Order Matters, And It Is Not The Order Of The Chapters

Look at the list above again. The front end is first because everything else works in recovered time. But the safe-idle gate is second — before the transaction detector, not after it.

That is not arbitrary. A transaction begun during reset must never reach the detector at all. Putting the gate downstream of the detector would mean the detector had already started a transaction that the gate then had to unwind — and there is nothing to unwind it with. Chapter 14.2's state machine has no "never mind" transition, and adding one would mean every downstream block needed to handle a transaction that retroactively did not happen.

Assembled SPI slave: the front end feeds a safe-idle gate, then the transaction detector and mode logic, then the receive and transmit paths, the framing block, the system interface and the output enableSCLK, CS#, MOSI14.1 front end14.8 safe idle14.2 transaction14.6 mode14.3 receive14.4 transmit14.7 framing14.9 system side14.5 output enablesoftwareMISO padstrobesgatedcapwordstx_datavalue12
Figure 1 — the assembled slave. One block touches a pin; everything to its right works in recovered time. The safe-idle gate sits between the front end and the transaction detector — the one ordering decision in the file, because the detector has no way to un-start a transaction. The dashed edge is the output enable taking the UNGATED select, for the reason Chapter 14.5 gives. The mode block feeds both data paths: a capture strobe to one and a launch strobe to the other, never on the same cycle.

The one other wiring decision worth stating is that the output enable takes the ungated select, deliberately. Chapter 14.5 argues it fully; the short version is that a stranded slave must still release MISO on time, and the gate exists to keep a transaction out of the receive path rather than to change what the output stage does.

2. What This Slave Requires Of A Master, All In One Place

Three numbers, all derived from SYNC_N, all measured rather than asserted, and none of them in any datasheet a protocol description would give you:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   LEAD  >= SYNC_N + 2     chip select to the first SCLK edge      (14.4)
   HALF  >= SYNC_N + 1     one SCLK half-period                    (14.4)
   GAP   >= SYNC_N + 1     chip select high between transactions    (14.5)

The lead is larger than the half-period because the first bit waits for Chapter 14.2's transaction boundary to be republished as a registered strobe and later bits do not. The gap requirement only bites against a neighbour that drives faster than this slave releases — two identical slaves never contend.

An integrator needs those three numbers and nothing else, which is why the slave publishes all of them as measured values alongside the flags: min_half, lead_seen, gap_seen. The parameters say what the design requires; the measurements say what the master actually supplied, and the difference is the margin.

3. The First Thing Integration Found: A Truncated Last Half-Period

The data was correct both ways on the very first run. And the slave reported ratio_err, having measured a shortest half-period of 2 where the programmed half-period was 4.

Neither design was broken. Neither chapter's own bench could have found it, because it is a property of the pair.

What happens is that Chapter 13's master ends a transaction on its final capture rather than a half-period after it. So the interval closed by the closing edge is short — measured at exactly 2 cycles, at every divisor:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   measured at the pins, divisor 12 (half-period 6):

   ... edge, 6 cycles, edge, 6 cycles, edge, 2 cycles, edge, CS rises
                                              ^^^^^^^^
                                              the closing interval

And crucially: it is a fixed 2 cycles regardless of the divisor. No choice of divisor makes it go away. A design that treated it as a violation would report a fault on every transaction that ever worked.

Fourteen system-clock cycles across six rows. An SCLK pin row rises on cycle 1, falls on cycle 5, rises on cycle 9 and falls on cycle 11. A chip-select pin row rises on cycle 12. An interval row reads 4, 4 and then 2. A measured-minimum row falls from 4 to 2. A ratio-error row stays low throughout.a data half-period: judgeda data half-period: judgedthe closing edge: 2 cyclesthe closing edge: 2 cyclesmeasured, not judgedmeasured, not judgedclksclk (pin)cs_n (pin)intervalmin_halfratio_errt0t1t2t3t4t5t6t7t8t9t10t11t12t13
Figure 2 — the finding, measured at the pins with a divisor of 8 so the half-period is 4. Every interval inside the clock train is 4 cycles; the one closed by the CLOSING edge is 2, and it is 2 at every divisor because the master ends on its final capture. That interval is never followed by another edge, so it is measured and never judged: min_half falls to 2 and ratio_err stays clear.

Why that edge carries no data

Under CPHA = 0 the slave captures on leading edges. Every bit has already been captured by the time the closing trailing edge arrives — that edge is only SCLK returning to idle. Nothing depends on it being a full half-period long.

So Chapter 14.1 was given the distinction it was missing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   min_half     the true shortest interval, INCLUDING the closing one.
                Not gated. The closing edge stays visible in the measurement.

   ratio_err    gated on intervals that were FOLLOWED BY MORE CLOCKING, which
                are exactly the intervals a capture depended on.

A flag and a number that deliberately disagree, with a reason on the record. An interval is a half-period only once another edge has followed it; until then it might be the last interval of the transaction, and the last interval is a different thing.

4. The Second Thing: A Real Master's MOSI Is One Cycle Late

Chapter 14.6's phase detector originally tested for exact coincidence — MOSI changing on the very cycle of the capture. Against the unit bench it was perfect: six motion events in a mismatched transaction, zero in a matched one.

Against this master it found nothing.

The probe result is short enough to quote:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   matched pair        MOSI settles 3 cycles BEFORE the capture
   mismatched pair     MOSI changes 1 cycle AFTER the capture

Because a real master registers its MOSI output. The bit appears one cycle after the edge that launched it, so a mismatched master does not change MOSI at the slave's sample — it changes it one cycle later. A coincidence test sees nothing at all.

The detector became a window, one cycle either side, and the width is provable: a matched master's bit settles at least HALF_MIN - 1 cycles before the sample, which at the legal minimum is 2 — outside a one-cycle window, by exactly one cycle. So the window is as wide as it can be while a matched pair still produces exactly zero events.

The unit bench now drives both output lags, 0 and 1, in both the matched and mismatched cases — so the case that matters on a bus is covered where it previously was not.

5. The Six Rules That Make It Synthesisable

The same rules as the master (Chapter 13.11), with one addition and one absence.

One clock, clk. SCLK is an input here rather than an output, and nothing is clocked on it — which is the whole content of Chapter 14.1's architecture choice.

One reset, asynchronous, and on the output enable it is mandatory: a slave held in reset while driving a shared bus makes every other device on that bus unusable.

No latches, no gated clocks, no combinational path from a pin to a pin.

Every register has exactly one driver, which in VHDL is enforced by the language and in SystemVerilog is a review item.

ADDITION: every asynchronous input reaches its first flop through a synchroniser chain of the SAME depth. That is Chapter 14.1's delay-matching requirement, and it is a lint rule rather than a timing one — no static timing tool will report it, because both paths are short and both pass.

ABSENCE: there is no generated clock to constrain, because the slave generates nothing. What it needs instead is input delay constraints on three pins, which is Chapter 15.7's subject and cannot be written from inside the RTL.

6. The Review Checklist That Would Gate It

Fourteen items. Each one is a question with a location, because a checklist whose items cannot be resolved by looking at a specific place is a list of good intentions.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  Is SYNC_N the SAME parameter for all three inputs, and passed once from
       the top?                                       14.1, and this file
    2  Is SYNC_N the same value in the safe-idle gate as in the front end? A
       mismatch silently changes the stale window.     14.8, and this file
    3  Does the constraints file have set_input_delay on SCLK, CS and MOSI, and
       NO create_clock on SCLK?                        outside the RTL
    4  Do the synchroniser chains carry ASYNC_REG (or the vendor equivalent) and
       an attribute preventing SRL inference?          outside the RTL
    5  Is the tri-state at the TOP level, driving a port?      this file, 14.5
    6  Is oe released by reset ASYNCHRONOUSLY?                 14.5
    7  Is the safe-idle gate BEFORE the transaction detector?  this file
    8  Does the output enable take the UNGATED select?         this file, 14.5
    9  Is every counter zeroed by the transaction START rather than by its end
       or by its own carry?                            14.3, 14.4, 14.7
   10  Does rx_valid_stb fire one cycle AFTER the final capture?   14.3
   11  Is the verdict sampled at txn_report_stb and never at txn_end_stb?
                                                       14.2, 14.6, 14.7
   12  Is the sequence number advanced by the FIRST word of a transaction?  14.9
   13  Is every measured value published alongside its flag?    all of them
   14  Are PTR_W and DEPTH consistent, and LEN_W wide enough for MAX_W?  14.9, 14.7

Items 1, 2 and 14 are all the same kind of finding — a relationship between two parameters that no single module can check — and all three are only visible at the top level. That is what makes a top-level review a distinct activity from reviewing nine blocks.

7. The Slave, Assembled — Three HDLs

The top level

Almost entirely structure, and for the same reason as the master's: every decision worth arguing about was made in Chapter 14.1 through Chapter 14.9, and logic here would mean one was made in the wrong place.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_slave_top.sv — nine blocks, one ordering decision, and one wiring subtlety
// spi_slave_top.sv
//
// Chapter 14.10 -- the whole slave, and the review that would gate it.
//
// Nine blocks, wired together. As with the master's top level (Chapter 13.11) this file
// is almost entirely structure, and for the same reason: every decision worth arguing
// about was made in Chapters 14.1 through 14.9, and logic here would mean one was made
// in the wrong place.
//
//     14.1  spi_slave_frontend    synchronise, recover edges, measure the ratio
//     14.8  spi_slave_safe_idle   refuse a transaction begun during reset
//     14.2  spi_slave_cs          the transaction boundary, and its classification
//     14.6  spi_slave_mode        capture and launch from CPHA alone, plus diagnosis
//     14.3  spi_slave_rx          capture MOSI, assemble words
//     14.7  spi_slave_frame       complete words and partials, kept apart
//     14.4  spi_slave_tx          launch MISO, including the awkward first bit
//     14.5  spi_slave_oe          drive only while selected, release in time
//     14.9  spi_slave_regs        the system interface, with a default and a seqlock
//
// THE ORDER MATTERS, AND IT IS NOT THE ORDER OF THE CHAPTERS.
//
// The front end comes first because everything else works in recovered time. The safe-
// idle gate comes SECOND -- before the transaction detector, not after -- because a
// transaction begun during reset must never reach the detector at all. Putting the gate
// downstream of the detector would mean the detector had already started a transaction
// that the gate then had to unwind, and there is nothing to unwind it with.
//
// WHAT THIS SLAVE REQUIRES OF A MASTER, ALL IN ONE PLACE.
//
// Three numbers, all derived from SYNC_N, all measured rather than asserted, and none of
// them in any datasheet a protocol description would give you:
//
//     LEAD  >= SYNC_N + 2     chip select to the first SCLK edge      (14.4)
//     HALF  >= SYNC_N + 1     one SCLK half-period                    (14.4)
//     GAP   >= SYNC_N + 1     chip select high between transactions    (14.5)
//
// The lead is larger than the half-period because the first bit waits for the
// transaction boundary of 14.2 to be republished as a registered strobe and later bits
// do not. The gap requirement only bites against a neighbour that drives faster than
// this slave releases -- two identical slaves never contend.
//
// A system integrator needs those three numbers and nothing else, which is why the slave
// publishes all of them as MEASURED values alongside the flags: `min_half`, `lead_seen`,
// `gap_seen`. The numbers in the parameters are what the design requires; the measured
// ones are what the master actually supplied, and the difference is the margin.
//
// WHAT IS SYNTHESISABLE ABOUT IT.
//
// The same six rules as the master (Chapter 13.11), with one addition and one absence:
//
//   * ONE CLOCK, `clk`. SCLK is an INPUT here rather than an output, and nothing is
//     clocked on it -- which is the whole content of Chapter 14.1's architecture choice.
//   * ONE RESET, asynchronous, and on the output enable it is mandatory: a slave held in
//     reset while driving a shared bus makes every other device unusable.
//   * NO LATCHES, no gated clocks, no combinational path from a pin to a pin.
//   * ADDITION: every asynchronous input reaches its first flop through a synchroniser
//     chain of the SAME depth, which is 14.1's delay-matching requirement and is a lint
//     rule rather than a timing one.
//   * ABSENCE: there is no generated clock to constrain, because the slave generates
//     nothing. What it needs instead is INPUT DELAY constraints on three pins, which is
//     Chapter 15.7's subject and cannot be written from inside the RTL.

module spi_slave_top #(
    parameter int MAX_W    = 32,
    parameter int LEN_W    = 6,
    parameter int CNT_W    = 12,
    parameter int SYNC_N   = 2,
    parameter int HALF_MIN = 3,
    parameter int LEAD_MIN = 3,
    parameter int GAP_MIN  = 3,
    parameter int DEPTH    = 4,
    parameter int PTR_W    = 2,
    parameter int SEQ_W    = 8
) (
    input  wire              clk,
    input  wire              rst_n,

    // --- configuration ----------------------------------------------------
    input  wire              cpol,
    input  wire              cpha,
    input  wire              lsb_first,
    input  wire [LEN_W-1:0]  len,
    input  wire [MAX_W-1:0]  tx_default,

    // --- the system side (14.9) -------------------------------------------
    input  wire [PTR_W-1:0]  sys_rd_idx,
    output wire [MAX_W-1:0]  sys_rd_data,
    output wire [PTR_W:0]    sys_rd_count,
    output wire [SEQ_W-1:0]  sys_rd_seq,
    input  wire              sys_tx_wr,
    input  wire [MAX_W-1:0]  sys_tx_data,
    output wire              sys_tx_ready,

    // --- the partial channel (14.7) ---------------------------------------
    output wire [MAX_W-1:0]  partial_data,
    output wire [LEN_W-1:0]  partial_bits,
    output wire              partial_valid_stb,

    // --- status: every fault this slave can detect ------------------------
    output wire              ratio_err,      // 14.1: SCLK too fast to recover
    output wire [CNT_W-1:0]  min_half,       // 14.1: the measured shortest half
    output wire              lead_short,     // 14.4: the master's t_CSS too short
    output wire [7:0]        lead_seen,      // 14.4: measured
    output wire              half_short,     // 14.4: the half-period too short
    output wire [7:0]        half_seen,      // 14.4: measured
    output wire              gap_short,      // 14.5: this slave's CS-high too short
    output wire [7:0]        gap_seen,       // 14.5: measured
    output wire              cpol_mismatch,  // 14.6: the master's CPOL differs
    output wire              phase_suspect,  // 14.6: MOSI moving at the sample
    output wire [3:0]        moved_seen,     // 14.6: measured
    output wire              aborted,        // 14.7: a transaction ended mid-word
    output wire              was_stranded,   // 14.8: reset landed mid-transaction
    output wire              tx_underrun,    // 14.9: a word was taken unloaded
    output wire              rx_overflow,    // 14.9: a word arrived with no room
    output wire              len_err,        // 14.7: an impossible frame width
    input  wire              clr_flags,

    // --- the pins ---------------------------------------------------------
    input  wire              sclk_pin,
    input  wire              cs_n_pin,
    input  wire              mosi_pin,
    output wire              miso_pad
);

    // --- 14.1 ---------------------------------------------------------------
    wire sclk_q, cs_active, mosi_q;
    wire edge_a_stb, edge_b_stb, cs_assert_stb, cs_deassert_stb;

    spi_slave_frontend #(.SYNC_N(SYNC_N), .HALF_MIN(HALF_MIN), .CNT_W(CNT_W))
    u_fe (
        .clk(clk), .rst_n(rst_n), .cpol(cpol),
        .sclk_pin(sclk_pin), .cs_n_pin(cs_n_pin), .mosi_pin(mosi_pin),
        .sclk_q(sclk_q), .cs_active(cs_active), .mosi_q(mosi_q),
        .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb),
        .cs_assert_stb(cs_assert_stb), .cs_deassert_stb(cs_deassert_stb),
        .min_half(min_half), .ratio_err(ratio_err), .clr_flags(clr_flags)
    );

    // --- 14.8, BEFORE the transaction detector ------------------------------
    wire g_cs_active, g_cs_assert_stb, g_cs_deassert_stb;
    wire participating, stranded;

    spi_slave_safe_idle #(.SYNC_N(SYNC_N)) u_si (
        .clk(clk), .rst_n(rst_n),
        .cs_active(cs_active), .cs_assert_stb(cs_assert_stb),
        .cs_deassert_stb(cs_deassert_stb),
        .g_cs_active(g_cs_active), .g_cs_assert_stb(g_cs_assert_stb),
        .g_cs_deassert_stb(g_cs_deassert_stb),
        .participating(participating), .stranded(stranded),
        .was_stranded(was_stranded), .state_id(),
        .clr_flags(clr_flags)
    );

    // --- 14.2 ---------------------------------------------------------------
    wire             txn_active, txn_start_stb, txn_end_stb, txn_report_stb;
    wire [CNT_W-1:0] edges_in_txn, frames_in_txn;
    wire             txn_clean, txn_trunc, txn_empty;

    spi_slave_cs #(.LEN_W(LEN_W), .CNT_W(CNT_W)) u_cs (
        .clk(clk), .rst_n(rst_n),
        .cs_assert_stb(g_cs_assert_stb), .cs_deassert_stb(g_cs_deassert_stb),
        .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb), .len(len),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .txn_end_stb(txn_end_stb),
        .edges_in_txn(edges_in_txn), .frames_in_txn(frames_in_txn),
        .txn_clean(txn_clean), .txn_trunc(txn_trunc), .txn_empty(txn_empty),
        .txn_report_stb(txn_report_stb), .state_id()
    );

    // --- 14.6 ---------------------------------------------------------------
    wire cap_stb, launch_stb, preload_stb;

    spi_slave_mode #(.SUSPECT_N(3), .CNT_W(4)) u_mode (
        .clk(clk), .rst_n(rst_n), .cpol(cpol), .cpha(cpha),
        .sclk_q(sclk_q), .mosi_q(mosi_q), .cs_assert_stb(g_cs_assert_stb),
        .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .txn_report_stb(txn_report_stb), .txn_clean(txn_clean),
        .txn_trunc(txn_trunc),
        .cap_stb(cap_stb), .launch_stb(launch_stb), .preload_stb(preload_stb),
        .cpol_mismatch(cpol_mismatch), .phase_suspect(phase_suspect),
        .moved_run(moved_seen), .trunc_run(), .clr_flags(clr_flags)
    );

    // --- 14.3 ---------------------------------------------------------------
    wire [MAX_W-1:0] rx_data, rx_partial_sr;
    wire             rx_valid_stb;
    wire [LEN_W-1:0] bit_idx;

    spi_slave_rx #(.MAX_W(MAX_W), .LEN_W(LEN_W), .CNT_W(CNT_W)) u_rx (
        .clk(clk), .rst_n(rst_n),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .cap_stb(cap_stb), .mosi_q(mosi_q),
        .len(len), .lsb_first(lsb_first),
        .rx_data(rx_data), .rx_valid_stb(rx_valid_stb),
        .bit_idx(bit_idx), .words_in_txn(), .rx_partial_sr(rx_partial_sr)
    );

    // --- 14.7 ---------------------------------------------------------------
    wire [MAX_W-1:0] word_data;
    wire             word_valid_stb;

    spi_slave_frame #(.MAX_W(MAX_W), .LEN_W(LEN_W), .CNT_W(CNT_W)) u_frame (
        .clk(clk), .rst_n(rst_n), .len(len),
        .txn_start_stb(txn_start_stb), .txn_report_stb(txn_report_stb),
        .txn_trunc(txn_trunc), .txn_empty(txn_empty),
        .rx_data(rx_data), .rx_valid_stb(rx_valid_stb), .bit_idx(bit_idx),
        .rx_partial_sr(rx_partial_sr),
        .word_data(word_data), .word_valid_stb(word_valid_stb),
        .words_complete(),
        .partial_data(partial_data), .partial_bits(partial_bits),
        .partial_valid_stb(partial_valid_stb),
        .len_err(len_err), .aborted(aborted), .state_id(),
        .clr_flags(clr_flags)
    );

    // --- 14.9 ---------------------------------------------------------------
    wire [MAX_W-1:0] tx_data;
    wire             word_taken_stb;

    spi_slave_regs #(.MAX_W(MAX_W), .DEPTH(DEPTH), .PTR_W(PTR_W), .SEQ_W(SEQ_W))
    u_regs (
        .clk(clk), .rst_n(rst_n),
        .txn_start_stb(txn_start_stb),
        .word_data(word_data), .word_valid_stb(word_valid_stb),
        .word_taken_stb(word_taken_stb),
        .sys_rd_idx(sys_rd_idx), .sys_rd_data(sys_rd_data),
        .sys_rd_count(sys_rd_count), .sys_rd_seq(sys_rd_seq),
        .sys_tx_wr(sys_tx_wr), .sys_tx_data(sys_tx_data),
        .sys_tx_ready(sys_tx_ready), .tx_default(tx_default),
        .tx_data(tx_data),
        .tx_underrun(tx_underrun), .rx_overflow(rx_overflow),
        .clr_flags(clr_flags)
    );

    // --- 14.4 ---------------------------------------------------------------
    wire miso_val;

    spi_slave_tx #(.MAX_W(MAX_W), .LEN_W(LEN_W), .LEAD_MIN(LEAD_MIN),
                   .HALF_MIN(HALF_MIN), .CNT_W(8)) u_tx (
        .clk(clk), .rst_n(rst_n),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .preload_stb(preload_stb), .launch_stb(launch_stb), .cap_stb(cap_stb),
        .len(len), .lsb_first(lsb_first), .tx_data(tx_data),
        .miso(miso_val),
        .word_taken_stb(word_taken_stb), .bits_driven(),
        .lead_seen(lead_seen), .lead_short(lead_short),
        .half_seen(half_seen), .half_short(half_short), .clr_flags(clr_flags)
    );

    // --- 14.5 --------------------------------------------------------------
    // The enable is derived from the UNGATED select, deliberately. A stranded slave
    // (14.8) must still release the bus properly and must still not drive it while
    // deselected -- and the gate exists to keep a transaction out of the receive path,
    // not to change what the output stage does. Wiring the enable to the gated select
    // would mean a stranded slave released MISO late, which is the one failure the
    // enable exists to prevent.
    spi_slave_oe #(.GAP_MIN(GAP_MIN), .CNT_W(8)) u_oe (
        .clk(clk), .rst_n(rst_n),
        .cs_active(cs_active), .cs_assert_stb(cs_assert_stb),
        .cs_deassert_stb(cs_deassert_stb),
        .miso_val(miso_val),
        .oe(), .miso_pad(miso_pad),
        .gap_seen(gap_seen), .gap_short(gap_short), .rel_cycles(),
        .clr_flags(clr_flags)
    );

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slave_top.v — the same design in Verilog-2001
// spi_slave_top.v
//
// Chapter 14.10 -- the whole slave, and the review that would gate it.
//
// Nine blocks, wired together. As with the master's top level (Chapter 13.11) this file
// is almost entirely structure, and for the same reason: every decision worth arguing
// about was made in Chapters 14.1 through 14.9, and logic here would mean one was made
// in the wrong place.
//
//     14.1  spi_slave_frontend    synchronise, recover edges, measure the ratio
//     14.8  spi_slave_safe_idle   refuse a transaction begun during reset
//     14.2  spi_slave_cs          the transaction boundary, and its classification
//     14.6  spi_slave_mode        capture and launch from CPHA alone, plus diagnosis
//     14.3  spi_slave_rx          capture MOSI, assemble words
//     14.7  spi_slave_frame       complete words and partials, kept apart
//     14.4  spi_slave_tx          launch MISO, including the awkward first bit
//     14.5  spi_slave_oe          drive only while selected, release in time
//     14.9  spi_slave_regs        the system interface, with a default and a seqlock
//
// THE ORDER MATTERS, AND IT IS NOT THE ORDER OF THE CHAPTERS.
//
// The front end comes first because everything else works in recovered time. The safe-
// idle gate comes SECOND -- before the transaction detector, not after -- because a
// transaction begun during reset must never reach the detector at all. Putting the gate
// downstream of the detector would mean the detector had already started a transaction
// that the gate then had to unwind, and there is nothing to unwind it with.
//
// WHAT THIS SLAVE REQUIRES OF A MASTER, ALL IN ONE PLACE.
//
// Three numbers, all derived from SYNC_N, all measured rather than asserted, and none of
// them in any datasheet a protocol description would give you:
//
//     LEAD  >= SYNC_N + 2     chip select to the first SCLK edge      (14.4)
//     HALF  >= SYNC_N + 1     one SCLK half-period                    (14.4)
//     GAP   >= SYNC_N + 1     chip select high between transactions    (14.5)
//
// The lead is larger than the half-period because the first bit waits for the
// transaction boundary of 14.2 to be republished as a registered strobe and later bits
// do not. The gap requirement only bites against a neighbour that drives faster than
// this slave releases -- two identical slaves never contend.
//
// A system integrator needs those three numbers and nothing else, which is why the slave
// publishes all of them as MEASURED values alongside the flags: `min_half`, `lead_seen`,
// `gap_seen`. The numbers in the parameters are what the design requires; the measured
// ones are what the master actually supplied, and the difference is the margin.
//
// WHAT IS SYNTHESISABLE ABOUT IT.
//
// The same six rules as the master (Chapter 13.11), with one addition and one absence:
//
//   * ONE CLOCK, `clk`. SCLK is an INPUT here rather than an output, and nothing is
//     clocked on it -- which is the whole content of Chapter 14.1's architecture choice.
//   * ONE RESET, asynchronous, and on the output enable it is mandatory: a slave held in
//     reset while driving a shared bus makes every other device unusable.
//   * NO LATCHES, no gated clocks, no combinational path from a pin to a pin.
//   * ADDITION: every asynchronous input reaches its first flop through a synchroniser
//     chain of the SAME depth, which is 14.1's delay-matching requirement and is a lint
//     rule rather than a timing one.
//   * ABSENCE: there is no generated clock to constrain, because the slave generates
//     nothing. What it needs instead is INPUT DELAY constraints on three pins, which is
//     Chapter 15.7's subject and cannot be written from inside the RTL.

module spi_slave_top #(
    parameter MAX_W    = 32,
    parameter LEN_W    = 6,
    parameter CNT_W    = 12,
    parameter SYNC_N   = 2,
    parameter HALF_MIN = 3,
    parameter LEAD_MIN = 3,
    parameter GAP_MIN  = 3,
    parameter DEPTH    = 4,
    parameter PTR_W    = 2,
    parameter SEQ_W    = 8
) (
    input  wire              clk,
    input  wire              rst_n,

    // --- configuration ----------------------------------------------------
    input  wire              cpol,
    input  wire              cpha,
    input  wire              lsb_first,
    input  wire [LEN_W-1:0]  len,
    input  wire [MAX_W-1:0]  tx_default,

    // --- the system side (14.9) -------------------------------------------
    input  wire [PTR_W-1:0]  sys_rd_idx,
    output wire [MAX_W-1:0]  sys_rd_data,
    output wire [PTR_W:0]    sys_rd_count,
    output wire [SEQ_W-1:0]  sys_rd_seq,
    input  wire              sys_tx_wr,
    input  wire [MAX_W-1:0]  sys_tx_data,
    output wire              sys_tx_ready,

    // --- the partial channel (14.7) ---------------------------------------
    output wire [MAX_W-1:0]  partial_data,
    output wire [LEN_W-1:0]  partial_bits,
    output wire              partial_valid_stb,

    // --- status: every fault this slave can detect ------------------------
    output wire              ratio_err,      // 14.1: SCLK too fast to recover
    output wire [CNT_W-1:0]  min_half,       // 14.1: the measured shortest half
    output wire              lead_short,     // 14.4: the master's t_CSS too short
    output wire [7:0]        lead_seen,      // 14.4: measured
    output wire              half_short,     // 14.4: the half-period too short
    output wire [7:0]        half_seen,      // 14.4: measured
    output wire              gap_short,      // 14.5: this slave's CS-high too short
    output wire [7:0]        gap_seen,       // 14.5: measured
    output wire              cpol_mismatch,  // 14.6: the master's CPOL differs
    output wire              phase_suspect,  // 14.6: MOSI moving at the sample
    output wire [3:0]        moved_seen,     // 14.6: measured
    output wire              aborted,        // 14.7: a transaction ended mid-word
    output wire              was_stranded,   // 14.8: reset landed mid-transaction
    output wire              tx_underrun,    // 14.9: a word was taken unloaded
    output wire              rx_overflow,    // 14.9: a word arrived with no room
    output wire              len_err,        // 14.7: an impossible frame width
    input  wire              clr_flags,

    // --- the pins ---------------------------------------------------------
    input  wire              sclk_pin,
    input  wire              cs_n_pin,
    input  wire              mosi_pin,
    output wire              miso_pad
);

    // --- 14.1 ---------------------------------------------------------------
    wire sclk_q, cs_active, mosi_q;
    wire edge_a_stb, edge_b_stb, cs_assert_stb, cs_deassert_stb;

    spi_slave_frontend #(.SYNC_N(SYNC_N), .HALF_MIN(HALF_MIN), .CNT_W(CNT_W))
    u_fe (
        .clk(clk), .rst_n(rst_n), .cpol(cpol),
        .sclk_pin(sclk_pin), .cs_n_pin(cs_n_pin), .mosi_pin(mosi_pin),
        .sclk_q(sclk_q), .cs_active(cs_active), .mosi_q(mosi_q),
        .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb),
        .cs_assert_stb(cs_assert_stb), .cs_deassert_stb(cs_deassert_stb),
        .min_half(min_half), .ratio_err(ratio_err), .clr_flags(clr_flags)
    );

    // --- 14.8, BEFORE the transaction detector ------------------------------
    wire g_cs_active, g_cs_assert_stb, g_cs_deassert_stb;
    wire participating, stranded;

    spi_slave_safe_idle #(.SYNC_N(SYNC_N)) u_si (
        .clk(clk), .rst_n(rst_n),
        .cs_active(cs_active), .cs_assert_stb(cs_assert_stb),
        .cs_deassert_stb(cs_deassert_stb),
        .g_cs_active(g_cs_active), .g_cs_assert_stb(g_cs_assert_stb),
        .g_cs_deassert_stb(g_cs_deassert_stb),
        .participating(participating), .stranded(stranded),
        .was_stranded(was_stranded), .state_id(),
        .clr_flags(clr_flags)
    );

    // --- 14.2 ---------------------------------------------------------------
    wire             txn_active, txn_start_stb, txn_end_stb, txn_report_stb;
    wire [CNT_W-1:0] edges_in_txn, frames_in_txn;
    wire             txn_clean, txn_trunc, txn_empty;

    spi_slave_cs #(.LEN_W(LEN_W), .CNT_W(CNT_W)) u_cs (
        .clk(clk), .rst_n(rst_n),
        .cs_assert_stb(g_cs_assert_stb), .cs_deassert_stb(g_cs_deassert_stb),
        .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb), .len(len),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .txn_end_stb(txn_end_stb),
        .edges_in_txn(edges_in_txn), .frames_in_txn(frames_in_txn),
        .txn_clean(txn_clean), .txn_trunc(txn_trunc), .txn_empty(txn_empty),
        .txn_report_stb(txn_report_stb), .state_id()
    );

    // --- 14.6 ---------------------------------------------------------------
    wire cap_stb, launch_stb, preload_stb;

    spi_slave_mode #(.SUSPECT_N(3), .CNT_W(4)) u_mode (
        .clk(clk), .rst_n(rst_n), .cpol(cpol), .cpha(cpha),
        .sclk_q(sclk_q), .mosi_q(mosi_q), .cs_assert_stb(g_cs_assert_stb),
        .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .txn_report_stb(txn_report_stb), .txn_clean(txn_clean),
        .txn_trunc(txn_trunc),
        .cap_stb(cap_stb), .launch_stb(launch_stb), .preload_stb(preload_stb),
        .cpol_mismatch(cpol_mismatch), .phase_suspect(phase_suspect),
        .moved_run(moved_seen), .trunc_run(), .clr_flags(clr_flags)
    );

    // --- 14.3 ---------------------------------------------------------------
    wire [MAX_W-1:0] rx_data, rx_partial_sr;
    wire             rx_valid_stb;
    wire [LEN_W-1:0] bit_idx;

    spi_slave_rx #(.MAX_W(MAX_W), .LEN_W(LEN_W), .CNT_W(CNT_W)) u_rx (
        .clk(clk), .rst_n(rst_n),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .cap_stb(cap_stb), .mosi_q(mosi_q),
        .len(len), .lsb_first(lsb_first),
        .rx_data(rx_data), .rx_valid_stb(rx_valid_stb),
        .bit_idx(bit_idx), .words_in_txn(), .rx_partial_sr(rx_partial_sr)
    );

    // --- 14.7 ---------------------------------------------------------------
    wire [MAX_W-1:0] word_data;
    wire             word_valid_stb;

    spi_slave_frame #(.MAX_W(MAX_W), .LEN_W(LEN_W), .CNT_W(CNT_W)) u_frame (
        .clk(clk), .rst_n(rst_n), .len(len),
        .txn_start_stb(txn_start_stb), .txn_report_stb(txn_report_stb),
        .txn_trunc(txn_trunc), .txn_empty(txn_empty),
        .rx_data(rx_data), .rx_valid_stb(rx_valid_stb), .bit_idx(bit_idx),
        .rx_partial_sr(rx_partial_sr),
        .word_data(word_data), .word_valid_stb(word_valid_stb),
        .words_complete(),
        .partial_data(partial_data), .partial_bits(partial_bits),
        .partial_valid_stb(partial_valid_stb),
        .len_err(len_err), .aborted(aborted), .state_id(),
        .clr_flags(clr_flags)
    );

    // --- 14.9 ---------------------------------------------------------------
    wire [MAX_W-1:0] tx_data;
    wire             word_taken_stb;

    spi_slave_regs #(.MAX_W(MAX_W), .DEPTH(DEPTH), .PTR_W(PTR_W), .SEQ_W(SEQ_W))
    u_regs (
        .clk(clk), .rst_n(rst_n),
        .txn_start_stb(txn_start_stb),
        .word_data(word_data), .word_valid_stb(word_valid_stb),
        .word_taken_stb(word_taken_stb),
        .sys_rd_idx(sys_rd_idx), .sys_rd_data(sys_rd_data),
        .sys_rd_count(sys_rd_count), .sys_rd_seq(sys_rd_seq),
        .sys_tx_wr(sys_tx_wr), .sys_tx_data(sys_tx_data),
        .sys_tx_ready(sys_tx_ready), .tx_default(tx_default),
        .tx_data(tx_data),
        .tx_underrun(tx_underrun), .rx_overflow(rx_overflow),
        .clr_flags(clr_flags)
    );

    // --- 14.4 ---------------------------------------------------------------
    wire miso_val;

    spi_slave_tx #(.MAX_W(MAX_W), .LEN_W(LEN_W), .LEAD_MIN(LEAD_MIN),
                   .HALF_MIN(HALF_MIN), .CNT_W(8)) u_tx (
        .clk(clk), .rst_n(rst_n),
        .txn_active(txn_active), .txn_start_stb(txn_start_stb),
        .preload_stb(preload_stb), .launch_stb(launch_stb), .cap_stb(cap_stb),
        .len(len), .lsb_first(lsb_first), .tx_data(tx_data),
        .miso(miso_val),
        .word_taken_stb(word_taken_stb), .bits_driven(),
        .lead_seen(lead_seen), .lead_short(lead_short),
        .half_seen(half_seen), .half_short(half_short), .clr_flags(clr_flags)
    );

    // --- 14.5 --------------------------------------------------------------
    // The enable is derived from the UNGATED select, deliberately. A stranded slave
    // (14.8) must still release the bus properly and must still not drive it while
    // deselected -- and the gate exists to keep a transaction out of the receive path,
    // not to change what the output stage does. Wiring the enable to the gated select
    // would mean a stranded slave released MISO late, which is the one failure the
    // enable exists to prevent.
    spi_slave_oe #(.GAP_MIN(GAP_MIN), .CNT_W(8)) u_oe (
        .clk(clk), .rst_n(rst_n),
        .cs_active(cs_active), .cs_assert_stb(cs_assert_stb),
        .cs_deassert_stb(cs_deassert_stb),
        .miso_val(miso_val),
        .oe(), .miso_pad(miso_pad),
        .gap_seen(gap_seen), .gap_short(gap_short), .rel_cycles(),
        .clr_flags(clr_flags)
    );

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slave_top.vhd — the same design in VHDL
-- spi_slave_top.vhd
--
-- Chapter 14.10 -- the whole slave, and the review that would gate it.
--
-- Nine blocks, wired together. As with the master's top level (Chapter 13.11) this file
-- is almost entirely structure, and for the same reason: every decision worth arguing
-- about was made in Chapters 14.1 through 14.9, and logic here would mean one was made
-- in the wrong place.
--
--     14.1  spi_slave_frontend    synchronise, recover edges, measure the ratio
--     14.8  spi_slave_safe_idle   refuse a transaction begun during reset
--     14.2  spi_slave_cs          the transaction boundary, and its classification
--     14.6  spi_slave_mode        capture and launch from CPHA alone, plus diagnosis
--     14.3  spi_slave_rx          capture MOSI, assemble words
--     14.7  spi_slave_frame       complete words and partials, kept apart
--     14.4  spi_slave_tx          launch MISO, including the awkward first bit
--     14.5  spi_slave_oe          drive only while selected, release in time
--     14.9  spi_slave_regs        the system interface, with a default and a seqlock
--
-- THE ORDER MATTERS, AND IT IS NOT THE ORDER OF THE CHAPTERS.
--
-- The front end comes first because everything else works in recovered time. The safe-
-- idle gate comes SECOND -- before the transaction detector, not after -- because a
-- transaction begun during reset must never reach the detector at all. Putting the gate
-- downstream of the detector would mean the detector had already started a transaction
-- that the gate then had to unwind, and there is nothing to unwind it with.
--
-- WHAT THIS SLAVE REQUIRES OF A MASTER, ALL IN ONE PLACE.
--
-- Three numbers, all derived from SYNC_N, all measured rather than asserted, and none of
-- them in any datasheet a protocol description would give you:
--
--     LEAD  >= SYNC_N + 2     chip select to the first SCLK edge      (14.4)
--     HALF  >= SYNC_N + 1     one SCLK half-period                    (14.4)
--     GAP   >= SYNC_N + 1     chip select high between transactions    (14.5)
--
-- The lead is larger than the half-period because the first bit waits for the
-- transaction boundary of 14.2 to be republished as a registered strobe and later bits
-- do not. The gap requirement only bites against a neighbour that drives faster than
-- this slave releases -- two identical slaves never contend.
--
-- A system integrator needs those three numbers and nothing else, which is why the slave
-- publishes all of them as MEASURED values alongside the flags: `min_half`, `lead_seen`,
-- `gap_seen`. The numbers in the parameters are what the design requires; the measured
-- ones are what the master actually supplied, and the difference is the margin.
--
-- WHAT IS SYNTHESISABLE ABOUT IT.
--
-- The same six rules as the master (Chapter 13.11), with one addition and one absence:
--
--   * ONE CLOCK, `clk`. SCLK is an INPUT here rather than an output, and nothing is
--     clocked on it -- which is the whole content of Chapter 14.1's architecture choice.
--   * ONE RESET, asynchronous, and on the output enable it is mandatory: a slave held in
--     reset while driving a shared bus makes every other device unusable.
--   * NO LATCHES, no gated clocks, no combinational path from a pin to a pin.
--   * ADDITION: every asynchronous input reaches its first flop through a synchroniser
--     chain of the SAME depth, which is 14.1's delay-matching requirement and is a lint
--     rule rather than a timing one.
--   * ABSENCE: there is no generated clock to constrain, because the slave generates
--     nothing. What it needs instead is INPUT DELAY constraints on three pins, which is
--     Chapter 15.7's subject and cannot be written from inside the RTL.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_slave_top is
    generic (
        MAX_W    : positive := 32;
        LEN_W    : positive := 6;
        CNT_W    : positive := 12;
        SYNC_N   : positive := 2;
        HALF_MIN : positive := 3;
        LEAD_MIN : positive := 3;
        GAP_MIN  : positive := 3;
        DEPTH    : positive := 4;
        PTR_W    : positive := 2;
        SEQ_W    : positive := 8
    );
    port (
        clk               : in  std_logic;
        rst_n             : in  std_logic;

        -- configuration
        cpol              : in  std_logic;
        cpha              : in  std_logic;
        lsb_first         : in  std_logic;
        len               : in  unsigned(LEN_W - 1 downto 0);
        tx_default        : in  std_logic_vector(MAX_W - 1 downto 0);

        -- the system side (14.9)
        sys_rd_idx        : in  unsigned(PTR_W - 1 downto 0);
        sys_rd_data       : out std_logic_vector(MAX_W - 1 downto 0);
        sys_rd_count      : out unsigned(PTR_W downto 0);
        sys_rd_seq        : out unsigned(SEQ_W - 1 downto 0);
        sys_tx_wr         : in  std_logic;
        sys_tx_data       : in  std_logic_vector(MAX_W - 1 downto 0);
        sys_tx_ready      : out std_logic;

        -- the partial channel (14.7)
        partial_data      : out std_logic_vector(MAX_W - 1 downto 0);
        partial_bits      : out unsigned(LEN_W - 1 downto 0);
        partial_valid_stb : out std_logic;

        -- status: every fault this slave can detect
        ratio_err         : out std_logic;                   -- 14.1: SCLK too fast
        min_half          : out unsigned(CNT_W - 1 downto 0);-- 14.1: measured
        lead_short        : out std_logic;                   -- 14.4: t_CSS too short
        lead_seen         : out unsigned(7 downto 0);        -- 14.4: measured
        half_short        : out std_logic;                   -- 14.4: half too short
        half_seen         : out unsigned(7 downto 0);        -- 14.4: measured
        gap_short         : out std_logic;                   -- 14.5: CS-high short
        gap_seen          : out unsigned(7 downto 0);        -- 14.5: measured
        cpol_mismatch     : out std_logic;                   -- 14.6: CPOL differs
        phase_suspect     : out std_logic;                   -- 14.6: MOSI in motion
        moved_seen        : out unsigned(3 downto 0);        -- 14.6: measured
        aborted           : out std_logic;                   -- 14.7: ended mid-word
        was_stranded      : out std_logic;                   -- 14.8: reset mid-txn
        tx_underrun       : out std_logic;                   -- 14.9: taken unloaded
        rx_overflow       : out std_logic;                   -- 14.9: no room
        len_err           : out std_logic;                   -- 14.7: impossible width
        clr_flags         : in  std_logic;

        -- the pins
        sclk_pin          : in  std_logic;
        cs_n_pin          : in  std_logic;
        mosi_pin          : in  std_logic;
        miso_pad          : out std_logic
    );
end entity;

architecture rtl of spi_slave_top is

    -- 14.1
    signal sclk_q, cs_active, mosi_q : std_logic;
    signal edge_a_stb, edge_b_stb, cs_assert_stb, cs_deassert_stb : std_logic;

    -- 14.8
    signal g_cs_active, g_cs_assert_stb, g_cs_deassert_stb : std_logic;
    signal participating_i, stranded_i : std_logic;

    -- 14.2
    signal txn_active, txn_start_stb, txn_end_stb, txn_report_stb : std_logic;
    signal edges_in_txn, frames_in_txn : unsigned(CNT_W - 1 downto 0);
    signal txn_clean, txn_trunc, txn_empty : std_logic;

    -- 14.6
    signal cap_stb, launch_stb, preload_stb : std_logic;
    signal trunc_run_i : unsigned(3 downto 0);

    -- 14.3
    signal rx_data, rx_partial_sr : std_logic_vector(MAX_W - 1 downto 0);
    signal rx_valid_stb : std_logic;
    signal bit_idx : unsigned(LEN_W - 1 downto 0);
    signal words_in_txn_i : unsigned(CNT_W - 1 downto 0);

    -- 14.7
    signal word_data : std_logic_vector(MAX_W - 1 downto 0);
    signal word_valid_stb : std_logic;
    signal words_complete_i : unsigned(CNT_W - 1 downto 0);

    -- 14.9
    signal tx_data : std_logic_vector(MAX_W - 1 downto 0);
    signal word_taken_stb : std_logic;

    -- 14.4
    signal miso_val : std_logic;
    signal bits_driven_i : unsigned(LEN_W - 1 downto 0);

    -- 14.5
    signal oe_i : std_logic;
    signal rel_cycles_i : unsigned(7 downto 0);

    -- state_id outputs, brought out to signals rather than left open, because a
    -- simulator that optimises an unread output away removes the one thing a waveform
    -- reviewer looks at first.
    signal si_state_id : unsigned(1 downto 0);
    signal cs_state_id : unsigned(2 downto 0);
    signal fr_state_id : unsigned(1 downto 0);

begin

    -- --- 14.1 -------------------------------------------------------------------
    u_fe : entity work.spi_slave_frontend
        generic map (SYNC_N => SYNC_N, HALF_MIN => HALF_MIN, CNT_W => CNT_W)
        port map (clk => clk, rst_n => rst_n, cpol => cpol,
                  sclk_pin => sclk_pin, cs_n_pin => cs_n_pin, mosi_pin => mosi_pin,
                  sclk_q => sclk_q, cs_active => cs_active, mosi_q => mosi_q,
                  edge_a_stb => edge_a_stb, edge_b_stb => edge_b_stb,
                  cs_assert_stb => cs_assert_stb,
                  cs_deassert_stb => cs_deassert_stb,
                  min_half => min_half, ratio_err => ratio_err,
                  clr_flags => clr_flags);

    -- --- 14.8, BEFORE the transaction detector ----------------------------------
    u_si : entity work.spi_slave_safe_idle
        generic map (SYNC_N => SYNC_N)
        port map (clk => clk, rst_n => rst_n,
                  cs_active => cs_active, cs_assert_stb => cs_assert_stb,
                  cs_deassert_stb => cs_deassert_stb,
                  g_cs_active => g_cs_active, g_cs_assert_stb => g_cs_assert_stb,
                  g_cs_deassert_stb => g_cs_deassert_stb,
                  participating => participating_i, stranded => stranded_i,
                  was_stranded => was_stranded, state_id => si_state_id,
                  clr_flags => clr_flags);

    -- --- 14.2 -------------------------------------------------------------------
    u_cs : entity work.spi_slave_cs
        generic map (LEN_W => LEN_W, CNT_W => CNT_W)
        port map (clk => clk, rst_n => rst_n,
                  cs_assert_stb => g_cs_assert_stb,
                  cs_deassert_stb => g_cs_deassert_stb,
                  edge_a_stb => edge_a_stb, edge_b_stb => edge_b_stb, len => len,
                  txn_active => txn_active, txn_start_stb => txn_start_stb,
                  txn_end_stb => txn_end_stb,
                  edges_in_txn => edges_in_txn, frames_in_txn => frames_in_txn,
                  txn_clean => txn_clean, txn_trunc => txn_trunc,
                  txn_empty => txn_empty, txn_report_stb => txn_report_stb,
                  state_id => cs_state_id);

    -- --- 14.6 -------------------------------------------------------------------
    u_mode : entity work.spi_slave_mode
        generic map (SUSPECT_N => 3, CNT_W => 4)
        port map (clk => clk, rst_n => rst_n, cpol => cpol, cpha => cpha,
                  sclk_q => sclk_q, mosi_q => mosi_q,
                  cs_assert_stb => g_cs_assert_stb,
                  edge_a_stb => edge_a_stb, edge_b_stb => edge_b_stb,
                  txn_active => txn_active, txn_start_stb => txn_start_stb,
                  txn_report_stb => txn_report_stb, txn_clean => txn_clean,
                  txn_trunc => txn_trunc,
                  cap_stb => cap_stb, launch_stb => launch_stb,
                  preload_stb => preload_stb,
                  cpol_mismatch => cpol_mismatch, phase_suspect => phase_suspect,
                  moved_run => moved_seen, trunc_run => trunc_run_i,
                  clr_flags => clr_flags);

    -- --- 14.3 -------------------------------------------------------------------
    u_rx : entity work.spi_slave_rx
        generic map (MAX_W => MAX_W, LEN_W => LEN_W, CNT_W => CNT_W)
        port map (clk => clk, rst_n => rst_n,
                  txn_active => txn_active, txn_start_stb => txn_start_stb,
                  cap_stb => cap_stb, mosi_q => mosi_q,
                  len => len, lsb_first => lsb_first,
                  rx_data => rx_data, rx_valid_stb => rx_valid_stb,
                  bit_idx => bit_idx, words_in_txn => words_in_txn_i,
                  rx_partial_sr => rx_partial_sr);

    -- --- 14.7 -------------------------------------------------------------------
    u_frame : entity work.spi_slave_frame
        generic map (MAX_W => MAX_W, LEN_W => LEN_W, CNT_W => CNT_W)
        port map (clk => clk, rst_n => rst_n, len => len,
                  txn_start_stb => txn_start_stb,
                  txn_report_stb => txn_report_stb,
                  txn_trunc => txn_trunc, txn_empty => txn_empty,
                  rx_data => rx_data, rx_valid_stb => rx_valid_stb,
                  bit_idx => bit_idx, rx_partial_sr => rx_partial_sr,
                  word_data => word_data, word_valid_stb => word_valid_stb,
                  words_complete => words_complete_i,
                  partial_data => partial_data, partial_bits => partial_bits,
                  partial_valid_stb => partial_valid_stb,
                  len_err => len_err, aborted => aborted,
                  state_id => fr_state_id, clr_flags => clr_flags);

    -- --- 14.9 -------------------------------------------------------------------
    u_regs : entity work.spi_slave_regs
        generic map (MAX_W => MAX_W, DEPTH => DEPTH, PTR_W => PTR_W, SEQ_W => SEQ_W)
        port map (clk => clk, rst_n => rst_n,
                  txn_start_stb => txn_start_stb,
                  word_data => word_data, word_valid_stb => word_valid_stb,
                  word_taken_stb => word_taken_stb,
                  sys_rd_idx => sys_rd_idx, sys_rd_data => sys_rd_data,
                  sys_rd_count => sys_rd_count, sys_rd_seq => sys_rd_seq,
                  sys_tx_wr => sys_tx_wr, sys_tx_data => sys_tx_data,
                  sys_tx_ready => sys_tx_ready, tx_default => tx_default,
                  tx_data => tx_data,
                  tx_underrun => tx_underrun, rx_overflow => rx_overflow,
                  clr_flags => clr_flags);

    -- --- 14.4 -------------------------------------------------------------------
    u_tx : entity work.spi_slave_tx
        generic map (MAX_W => MAX_W, LEN_W => LEN_W, LEAD_MIN => LEAD_MIN,
                     HALF_MIN => HALF_MIN, CNT_W => 8)
        port map (clk => clk, rst_n => rst_n,
                  txn_active => txn_active, txn_start_stb => txn_start_stb,
                  preload_stb => preload_stb, launch_stb => launch_stb,
                  cap_stb => cap_stb,
                  len => len, lsb_first => lsb_first, tx_data => tx_data,
                  miso => miso_val,
                  word_taken_stb => word_taken_stb, bits_driven => bits_driven_i,
                  lead_seen => lead_seen, lead_short => lead_short,
                  half_seen => half_seen, half_short => half_short,
                  clr_flags => clr_flags);

    -- --- 14.5 -------------------------------------------------------------------
    -- The enable is derived from the UNGATED select, deliberately. A stranded slave
    -- (14.8) must still release the bus properly and must still not drive it while
    -- deselected -- and the gate exists to keep a transaction out of the receive path,
    -- not to change what the output stage does. Wiring the enable to the gated select
    -- would mean a stranded slave released MISO late, which is the one failure the
    -- enable exists to prevent.
    u_oe : entity work.spi_slave_oe
        generic map (GAP_MIN => GAP_MIN, CNT_W => 8)
        port map (clk => clk, rst_n => rst_n,
                  cs_active => cs_active, cs_assert_stb => cs_assert_stb,
                  cs_deassert_stb => cs_deassert_stb,
                  miso_val => miso_val,
                  oe => oe_i, miso_pad => miso_pad,
                  gap_seen => gap_seen, gap_short => gap_short,
                  rel_cycles => rel_cycles_i, clr_flags => clr_flags);

end architecture;

The integration testbench

Six tests. The master is Module 13's, unmodified, driven through its register map.

  1. One byte each way, with the requirements respected. A divisor of 8, and a lead, lag and gap of 6 — all comfortably above the three numbers.
  2. The requirements are met, and the slave says so with numbers — one of which disagrees with its own flag. min_half reports 2 and ratio_err stays clear. §3 is why, and the test asserts both halves: no violation reported, and the measurement showing 2.
  3. A four-byte transaction in both directions, which is what a real device does — a command byte and a response.
  4. All four modes, both sides configured to agree, with no mode mismatch reported.
  5. Now program the master badly, one requirement at a time, and check the slave names the violation:
    • a lead of 1 — reported as a short lead, with the measured interval;
    • a divisor of 4 — a half-period of 2 on every interval rather than just the last one, which is a different fault from §3's and lands in a different flag;
    • CPOL=1 against a slave at CPOL=0 — caught before a bit has moved;
    • CPHA=1 against a slave at CPHA=0 — diagnosed inside one transaction from MOSI being in motion, while the same transaction is classified txn_clean, so no amount of edge counting would have found it.
  6. And back to a good configuration, to show the system recovers: a misconfiguration is a diagnosis, not damage.
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slave_top_tb.sv — the master of Chapter 13.11, unmodified, driven through its own register map
// spi_slave_top_tb.sv
//
// The master in this testbench is not a model. It is `spi_master_top` from Chapter 13.11,
// byte for byte -- the design that module built and verified -- driven through its own
// register interface exactly as software would drive it.
//
// That is the point of the test, and it is the strongest thing either module can say. Two
// designs built and verified independently, against pin-level requirements rather than
// against each other, now wired together with nothing in between:
//
//     master.sclk  -> slave.sclk_pin
//     master.mosi  -> slave.mosi_pin
//     master.cs_n0 -> slave.cs_n_pin
//     slave.miso   -> master.miso
//
// Neither has been adjusted to suit the other. What makes them work is that Chapter 14
// derived three numbers the master has to respect and Chapter 13 made all three
// programmable -- so the integration is a matter of writing the right values into the
// master's TIMING register, and the slave reports whether they were right.
//
// The test then does the thing a system does: it PROGRAMS THE MASTER BADLY on purpose and
// checks the slave says which requirement was violated.

`timescale 1ns/1ps

module spi_slave_top_tb;

    localparam int MAX_W  = 32;
    localparam int LEN_W  = 6;
    localparam int CNT_W  = 12;
    localparam int SYNC_N = 2;
    localparam int PTR_W  = 2;
    localparam int SEQ_W  = 8;

    // The master's register map, from Chapter 13.11.
    localparam [4:0] A_CTRL   = 5'h00, A_TIMING = 5'h04, A_TXDATA = 5'h08,
                     A_TXLAST = 5'h0C, A_RXDATA = 5'h10, A_STATUS = 5'h14,
                     A_CMD    = 5'h18;
    localparam int S_TXRDY = 0, S_RXRDY = 1, S_BUSY = 2;

    logic clk = 1'b0;
    logic rst_n = 1'b0;
    always #5 clk = ~clk;

    // --- the shared pins ----------------------------------------------------
    wire sclk, mosi, miso;
    wire [3:0] cs_n;

    // --- the master: Chapter 13.11, unmodified ------------------------------
    logic [4:0]  m_addr  = 5'h0;
    logic        m_wr    = 1'b0;
    logic        m_rd    = 1'b0;
    logic [31:0] m_wdata = 32'h0;
    wire  [31:0] m_rdata;

    spi_master_top #(.MAX_W(32), .LEN_W(6), .DIV_W(8), .CNT_W(8), .N_CS(4),
                     .SEL_W(2)) u_master (
        .clk(clk), .rst_n(rst_n),
        .reg_addr(m_addr), .reg_wr(m_wr), .reg_rd(m_rd),
        .reg_wdata(m_wdata), .reg_rdata(m_rdata),
        .sclk(sclk), .mosi(mosi), .miso(miso), .cs_n(cs_n)
    );

    // --- the slave: Chapter 14.10 -------------------------------------------
    logic             s_cpol      = 1'b0;
    logic             s_cpha      = 1'b0;
    logic             s_lsb       = 1'b0;
    logic [LEN_W-1:0] s_len       = 6'd8;
    logic [MAX_W-1:0] s_default   = 32'hFF;
    logic [PTR_W-1:0] s_rd_idx    = 2'd0;
    logic             s_tx_wr     = 1'b0;
    logic [MAX_W-1:0] s_tx_data   = 32'h0;
    logic             s_clr       = 1'b0;

    wire [MAX_W-1:0] s_rd_data;
    wire [PTR_W:0]   s_rd_count;
    wire [SEQ_W-1:0] s_rd_seq;
    wire             s_tx_ready;
    wire [MAX_W-1:0] s_partial_data;
    wire [LEN_W-1:0] s_partial_bits;
    wire             s_partial_valid;
    wire             s_ratio_err, s_lead_short, s_half_short, s_gap_short;
    wire [CNT_W-1:0] s_min_half;
    wire [7:0]       s_lead_seen, s_half_seen, s_gap_seen;
    wire             s_cpol_mismatch, s_phase_suspect, s_aborted;
    wire [3:0]       s_moved_seen;
    wire             s_was_stranded, s_tx_underrun, s_rx_overflow, s_len_err;

    spi_slave_top #(.MAX_W(MAX_W), .LEN_W(LEN_W), .CNT_W(CNT_W), .SYNC_N(SYNC_N),
                    .HALF_MIN(3), .LEAD_MIN(3), .GAP_MIN(3),
                    .DEPTH(4), .PTR_W(PTR_W), .SEQ_W(SEQ_W)) u_slave (
        .clk(clk), .rst_n(rst_n),
        .cpol(s_cpol), .cpha(s_cpha), .lsb_first(s_lsb), .len(s_len),
        .tx_default(s_default),
        .sys_rd_idx(s_rd_idx), .sys_rd_data(s_rd_data),
        .sys_rd_count(s_rd_count), .sys_rd_seq(s_rd_seq),
        .sys_tx_wr(s_tx_wr), .sys_tx_data(s_tx_data),
        .sys_tx_ready(s_tx_ready),
        .partial_data(s_partial_data), .partial_bits(s_partial_bits),
        .partial_valid_stb(s_partial_valid),
        .ratio_err(s_ratio_err), .min_half(s_min_half),
        .lead_short(s_lead_short), .lead_seen(s_lead_seen),
        .half_short(s_half_short), .half_seen(s_half_seen),
        .gap_short(s_gap_short), .gap_seen(s_gap_seen),
        .cpol_mismatch(s_cpol_mismatch), .phase_suspect(s_phase_suspect),
        .moved_seen(s_moved_seen),
        .aborted(s_aborted), .was_stranded(s_was_stranded),
        .tx_underrun(s_tx_underrun), .rx_overflow(s_rx_overflow),
        .len_err(s_len_err), .clr_flags(s_clr),
        .sclk_pin(sclk), .cs_n_pin(cs_n[0]), .mosi_pin(mosi), .miso_pad(miso)
    );

    integer errors = 0;

    // A watchdog, because a simulation that never terminates reports nothing.
    initial begin
        #4_000_000;
        $display("FAIL: the simulation did not finish within its time limit");
        $finish;
    end

    task automatic adv(input integer n);
        begin repeat (n) @(negedge clk); end
    endtask

    // --- the master's software side -----------------------------------------
    logic [31:0] m_q;

    task automatic m_write(input [4:0] a, input [31:0] d);
        begin
            @(negedge clk);
            m_addr = a; m_wdata = d; m_wr = 1'b1;
            @(negedge clk);
            m_wr = 1'b0;
        end
    endtask

    task automatic m_read(input [4:0] a);
        begin
            @(negedge clk);
            m_addr = a; m_rd = 1'b1;
            #1 m_q = m_rdata;
            @(negedge clk);
            m_rd = 1'b0;
        end
    endtask

    // Programs the master. `div` is the SCLK period, so the half-period the slave sees
    // is div/2 -- which is how the master's divisor and the slave's HALF requirement meet.
    task automatic m_configure(input integer div, input bit pol, input bit pha,
                               input bit lsb, input integer nbits,
                               input integer lead, input integer lag,
                               input integer gap);
        begin
            m_write(A_CTRL, (pol ? 32'h1 : 32'h0) | (pha ? 32'h2 : 32'h0) |
                            (lsb ? 32'h4 : 32'h0) |
                            ((div & 32'hFF) << 8) | ((nbits & 32'h3F) << 16));
            m_write(A_TIMING, (lead & 32'hFF) | ((lag & 32'hFF) << 8) |
                              ((gap & 32'hFF) << 16));
        end
    endtask

    integer m_got_n;
    logic [31:0] m_got [0:31];

    task automatic m_collect;
        begin
            m_read(A_STATUS);
            while (m_q[S_RXRDY]) begin
                m_read(A_RXDATA);
                m_got[m_got_n] = m_q;
                m_got_n = m_got_n + 1;
                m_read(A_STATUS);
            end
        end
    endtask

    task automatic m_send(input [31:0] d, input bit last);
        integer guard;
        begin
            guard = 8000;
            m_read(A_STATUS);
            while (!m_q[S_TXRDY] && guard > 0) begin
                m_read(A_STATUS);
                guard = guard - 1;
            end
            if (guard == 0) begin
                $display("  FAIL: the master's queue never became ready");
                errors = errors + 1;
            end
            m_write(last ? A_TXLAST : A_TXDATA, d);
        end
    endtask

    task automatic m_wait_done;
        integer guard;
        begin
            guard = 8000;
            m_read(A_STATUS);
            while (!m_q[S_BUSY] && guard > 0) begin
                m_collect();
                m_read(A_STATUS);
                guard = guard - 1;
            end
            guard = 20000;
            m_read(A_STATUS);
            while (m_q[S_BUSY] && guard > 0) begin
                m_collect();
                m_read(A_STATUS);
                guard = guard - 1;
            end
            adv(12);
            m_collect();
        end
    endtask

    // --- the slave's software side ------------------------------------------
    task automatic s_write(input [MAX_W-1:0] v);
        integer guard;
        begin
            guard = 4000;
            while (!s_tx_ready && guard > 0) begin
                @(negedge clk);
                guard = guard - 1;
            end
            s_tx_data = v;
            s_tx_wr   = 1'b1;
            @(negedge clk);
            s_tx_wr   = 1'b0;
        end
    endtask

    task automatic clear_both;
        begin
            s_clr = 1'b1; adv(1); s_clr = 1'b0;
            m_write(A_CMD, 32'h2);
            adv(2);
        end
    endtask

    integer k, bad;

    initial begin
        m_got_n = 0;

        adv(3);
        rst_n = 1'b1;
        adv(4);

        // 1. ONE BYTE, EACH WAY, WITH THE REQUIREMENTS RESPECTED. A divisor of 8 gives a
        //    half-period of 4, and a lead, lag and gap of 6 -- all comfortably above the
        //    three numbers the slave requires.
        clear_both();
        m_configure(8, 1'b0, 1'b0, 1'b0, 8, 6, 6, 6);
        s_write(32'h5A);
        m_got_n = 0;
        m_send(32'hA5, 1'b1);
        m_wait_done();

        if (s_rd_count != 1 || s_rd_data[7:0] !== 8'hA5) begin
            $display("  FAIL: the slave received %0d words, the first being %02h, expected 1 and a5",
                     s_rd_count, s_rd_data[7:0]);
            errors = errors + 1;
        end
        if (m_got_n != 1 || m_got[0][7:0] !== 8'h5A) begin
            $display("  FAIL: the master read %0d words, the first being %02h, expected 1 and 5a",
                     m_got_n, m_got[0][7:0]);
            errors = errors + 1;
        end
        $display("  the master sent a5 and the slave received a5; the slave sent 5a and the master received 5a -- two independently verified designs, wired together and unmodified");

        // 2. THE REQUIREMENTS ARE MET, AND THE SLAVE SAYS SO WITH NUMBERS -- one of
        //    which disagrees with its own flag, on purpose.
        //
        //    `min_half` reports 2 where the programmed half-period is 4, and
        //    `ratio_err` stays clear. Both are correct, and the reason is the thing
        //    this chapter exists to find.
        //
        //    Chapter 13's master ends a transaction on its final capture rather than a
        //    half-period after it, so the interval closed by the CLOSING edge is short
        //    -- measured here, and at every divisor, as exactly 2 cycles. That edge
        //    carries no data in CPHA=0: every bit has already been captured on a
        //    leading edge, and the closing edge is only SCLK returning to idle.
        //
        //    So Chapter 14.1 measures it and does not judge it. Had it judged every
        //    interval, `ratio_err` would fire on every transaction that ever worked --
        //    and a flag that is always set is a flag nobody reads. Had it not measured
        //    it, the closing edge would be invisible and this paragraph could not have
        //    been written.
        //
        //    Neither design was changed to make this pass. The front end was given the
        //    distinction it was missing, and the distinction came from the measurement.
        if (s_ratio_err || s_lead_short || s_half_short || s_gap_short) begin
            $display("  FAIL: a correctly programmed master was reported as violating a requirement: ratio=%0b lead=%0b half=%0b gap=%0b",
                     s_ratio_err, s_lead_short, s_half_short, s_gap_short);
            errors = errors + 1;
        end
        if (s_min_half != 12'd2) begin
            $display("  FAIL: the shortest interval measured %0d, expected 2 -- the master's closing edge is meant to be visible in the measurement",
                     s_min_half);
            errors = errors + 1;
        end
        $display("  the slave reports no violation, with measured values: lead %0d, half %0d, gap %0d",
                 s_lead_seen, s_half_seen, s_gap_seen);
        $display("  and the shortest interval it saw was %0d, against a programmed half-period of 4, because the master ends a transaction on its final capture and returns SCLK to idle early -- measured and deliberately not judged, since that closing edge carries no data and a flag that fired on every working transaction would be a flag nobody reads",
                 s_min_half);

        // 3. A MULTI-BYTE TRANSACTION IN BOTH DIRECTIONS, which is what a real device
        //    does -- a command byte and a response.
        clear_both();
        m_got_n = 0;
        s_write(32'hDE);
        m_send(32'h03, 1'b0);      // a flash-like READ opcode
        m_collect();
        s_write(32'hAD);
        m_send(32'h12, 1'b0);
        m_collect();
        s_write(32'hBE);
        m_send(32'h34, 1'b0);
        m_collect();
        s_write(32'hEF);
        m_send(32'h56, 1'b1);
        m_collect();
        m_wait_done();

        if (s_rd_count != 4) begin
            $display("  FAIL: a four-byte transaction gave the slave %0d words",
                     s_rd_count);
            errors = errors + 1;
        end else begin
            bad = 0;
            for (k = 0; k < 4; k = k + 1) begin
                s_rd_idx = k[PTR_W-1:0];
                adv(1);
                case (k)
                    0: if (s_rd_data[7:0] !== 8'h03) bad = bad + 1;
                    1: if (s_rd_data[7:0] !== 8'h12) bad = bad + 1;
                    2: if (s_rd_data[7:0] !== 8'h34) bad = bad + 1;
                    3: if (s_rd_data[7:0] !== 8'h56) bad = bad + 1;
                endcase
            end
            if (bad != 0) begin
                $display("  FAIL: %0d of the slave's four received bytes were wrong", bad);
                errors = errors + 1;
            end
        end
        if (m_got_n != 4 || m_got[0][7:0] !== 8'hDE || m_got[1][7:0] !== 8'hAD ||
            m_got[2][7:0] !== 8'hBE || m_got[3][7:0] !== 8'hEF) begin
            $display("  FAIL: the master read %0d bytes: %02h %02h %02h %02h",
                     m_got_n, m_got[0][7:0], m_got[1][7:0], m_got[2][7:0],
                     m_got[3][7:0]);
            errors = errors + 1;
        end
        if (s_aborted || s_tx_underrun || s_rx_overflow) begin
            $display("  FAIL: a clean four-byte transaction set a fault flag");
            errors = errors + 1;
        end
        $display("  a four-byte transaction under one chip select: the slave received 03 12 34 56 and the master received de ad be ef, with no fault flag set");

        // 4. ALL FOUR MODES, both sides configured to agree.
        for (k = 0; k < 4; k = k + 1) begin
            clear_both();
            s_cpol = k[0]; s_cpha = k[1];
            adv(2);
            m_configure(8, k[0], k[1], 1'b0, 8, 6, 6, 6);
            m_got_n = 0;
            s_write(32'h3C);
            m_send(32'hC3, 1'b1);
            m_wait_done();
            // Test 3 left the read index at the end of its sweep. `sys_rd_idx` is a
            // combinational window into the buffer, not a pointer the slave advances,
            // so a read of word 0 has to ASK for word 0.
            s_rd_idx = {PTR_W{1'b0}};
            adv(2);
            if (s_rd_data[7:0] !== 8'hC3) begin
                $display("  FAIL: mode %0d -- the slave received %02h, expected c3",
                         k, s_rd_data[7:0]);
                errors = errors + 1;
            end
            if (m_got_n != 1 || m_got[0][7:0] !== 8'h3C) begin
                $display("  FAIL: mode %0d -- the master received %02h, expected 3c",
                         k, m_got[0][7:0]);
                errors = errors + 1;
            end
            if (s_cpol_mismatch || s_phase_suspect) begin
                $display("  FAIL: mode %0d -- a matched pair reported a mode mismatch",
                         k);
                errors = errors + 1;
            end
        end
        s_cpol = 1'b0; s_cpha = 1'b0;
        adv(2);
        $display("  all four modes with both sides configured to agree: correct data both ways and no mode mismatch reported");

        // 5. NOW PROGRAM THE MASTER BADLY, ONE REQUIREMENT AT A TIME, and check the slave
        //    names the violation. This is what the measured outputs are for.
        //
        //    (a) too short a lead.
        clear_both();
        m_configure(8, 1'b0, 1'b0, 1'b0, 8, 1, 6, 6);
        s_write(32'h11);
        m_got_n = 0;
        m_send(32'h22, 1'b1);
        m_wait_done();
        if (!s_lead_short) begin
            $display("  FAIL: a lead of 1 was not reported, with a measured lead of %0d",
                     s_lead_seen);
            errors = errors + 1;
        end
        $display("  a master programmed with a lead of 1: the slave reports a short lead, measuring %0d recovered cycles",
                 s_lead_seen);

        //    (b) too fast a clock -- a divisor of 4 gives a half-period of 2, below
        //        the SYNC_N + 1 the slave needs on EVERY half-period rather than just
        //        the last one, which is a different fault from the one in test 2 and
        //        lands in a different flag.
        clear_both();
        m_configure(4, 1'b0, 1'b0, 1'b0, 8, 6, 6, 6);
        s_write(32'h33);
        m_got_n = 0;
        m_send(32'h44, 1'b1);
        m_wait_done();
        if (!s_half_short) begin
            $display("  FAIL: a half-period of 2 was not reported, measuring %0d",
                     s_half_seen);
            errors = errors + 1;
        end
        $display("  a master programmed with a divisor of 4, giving a half-period of 2: the slave reports a short half-period, measuring %0d",
                 s_half_seen);

        //    (c) a polarity mismatch -- the master idles SCLK high and the slave expects
        //        it low. Caught before a single bit moves.
        clear_both();
        m_configure(8, 1'b1, 1'b0, 1'b0, 8, 6, 6, 6);   // master CPOL=1
        s_cpol = 1'b0;                                   // slave still CPOL=0
        adv(2);
        s_write(32'h55);
        m_got_n = 0;
        m_send(32'h66, 1'b1);
        m_wait_done();
        if (!s_cpol_mismatch) begin
            $display("  FAIL: a polarity mismatch between the two designs was not reported");
            errors = errors + 1;
        end
        $display("  the master programmed CPOL=1 against a slave configured CPOL=0: the slave reports a polarity mismatch");

        //    (d) a phase mismatch. This is the one that cannot be found by counting,
        //        and the integration is where that matters most: the master here sends
        //        a whole number of frames, the slave agrees it received a whole number
        //        of frames, and the transaction is still wrong. What gives it away is
        //        that MOSI is in motion at the instant the slave samples it.
        clear_both();
        s_cpol = 1'b0; s_cpha = 1'b0;
        adv(2);
        m_configure(8, 1'b0, 1'b1, 1'b0, 8, 6, 6, 6);   // master CPHA=1
        s_write(32'h77);
        m_got_n = 0;
        m_send(32'h88, 1'b1);                            // 0x88 has transitions in it
        m_wait_done();
        if (!s_phase_suspect) begin
            $display("  FAIL: a phase-mismatched master was not diagnosed, with %0d motion events",
                     s_moved_seen);
            errors = errors + 1;
        end
        if (s_aborted) begin
            $display("  FAIL: a phase mismatch was reported as an abort, which would send the diagnosis to the wrong place");
            errors = errors + 1;
        end
        $display("  the master programmed CPHA=1 against a slave configured CPHA=0: diagnosed inside ONE transaction, from MOSI being in motion at %0d of the captures -- and the transaction was NOT flagged as an abort, because the master sent a whole number of frames and every edge arrived",
                 s_moved_seen);

        // 6. AND BACK TO A GOOD CONFIGURATION, to show the system recovers -- a
        //    misconfiguration is a diagnosis, not damage.
        clear_both();
        s_cpol = 1'b0; s_cpha = 1'b0;
        adv(2);
        m_configure(8, 1'b0, 1'b0, 1'b0, 8, 6, 6, 6);
        s_write(32'h99);
        m_got_n = 0;
        m_send(32'hAA, 1'b1);
        m_wait_done();
        if (s_rd_data[7:0] !== 8'hAA || m_got_n != 1 || m_got[0][7:0] !== 8'h99) begin
            $display("  FAIL: after four misconfigurations the system did not recover: slave got %02h, master got %02h",
                     s_rd_data[7:0], m_got[0][7:0]);
            errors = errors + 1;
        end
        if (s_ratio_err || s_lead_short || s_half_short || s_cpol_mismatch ||
            s_phase_suspect || s_aborted) begin
            $display("  FAIL: a good configuration after four bad ones still reports a fault");
            errors = errors + 1;
        end
        $display("  a good configuration after four bad ones works immediately and reports nothing: a misconfiguration is a diagnosis rather than damage");

        if (errors == 0)
            $display("PASS: the master of Chapter 13.11 and the slave of Chapter 14.10 are wired together unmodified, each verified independently against pin-level requirements rather than against the other, and they interoperate on the first attempt -- a byte each way, a four-byte transaction under one chip select in both directions, and all four modes with both sides agreeing, with no fault flag set anywhere -- because Chapter 14 derived three numbers a master must respect and Chapter 13 made all three programmable, so integration is a matter of writing the right values into the master's timing register -- and integration also found the one thing neither bench could have found on its own, that this master returns SCLK to idle two cycles after its final capture at every divisor, which is why Chapter 14.1 measures the closing interval and deliberately declines to judge it: the closing edge carries no data, and a flag that fired on every transaction that ever worked would be a flag nobody reads -- and when the master is programmed BADLY the slave names which requirement was violated: a lead of one is reported as a short lead with the measured interval, a divisor of four is reported as a short half-period, a polarity disagreement is caught before a bit has moved, and a phase disagreement -- which sends a whole number of frames and so cannot be found by counting anything -- is diagnosed inside a single transaction from MOSI being in motion within one cycle of the moment the slave samples it -- after which a good configuration works immediately, because a misconfiguration is a diagnosis and not damage");
        else
            $display("FAIL: %0d error(s)", errors);
        $finish;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slave_top_tb.v — the same bench in Verilog-2001
// spi_slave_top_tb.v
//
// The master in this testbench is not a model. It is `spi_master_top` from Chapter 13.11,
// byte for byte -- the design that module built and verified -- driven through its own
// register interface exactly as software would drive it.
//
// That is the point of the test, and it is the strongest thing either module can say. Two
// designs built and verified independently, against pin-level requirements rather than
// against each other, now wired together with nothing in between:
//
//     master.sclk  -> slave.sclk_pin
//     master.mosi  -> slave.mosi_pin
//     master.cs_n0 -> slave.cs_n_pin
//     slave.miso   -> master.miso
//
// Neither has been adjusted to suit the other. What makes them work is that Chapter 14
// derived three numbers the master has to respect and Chapter 13 made all three
// programmable -- so the integration is a matter of writing the right values into the
// master's TIMING register, and the slave reports whether they were right.
//
// The test then does the thing a system does: it PROGRAMS THE MASTER BADLY on purpose and
// checks the slave says which requirement was violated.

`timescale 1ns/1ps

module spi_slave_top_tb;

    localparam MAX_W  = 32;
    localparam LEN_W  = 6;
    localparam CNT_W  = 12;
    localparam SYNC_N = 2;
    localparam PTR_W  = 2;
    localparam SEQ_W  = 8;

    // The master's register map, from Chapter 13.11.
    localparam [4:0] A_CTRL   = 5'h00, A_TIMING = 5'h04, A_TXDATA = 5'h08,
                     A_TXLAST = 5'h0C, A_RXDATA = 5'h10, A_STATUS = 5'h14,
                     A_CMD    = 5'h18;
    localparam S_TXRDY = 0, S_RXRDY = 1, S_BUSY = 2;

    reg clk;
    reg rst_n;
    always #5 clk = ~clk;

    // --- the shared pins ----------------------------------------------------
    wire sclk, mosi, miso;
    wire [3:0] cs_n;

    // --- the master: Chapter 13.11, unmodified ------------------------------
    reg [4:0]  m_addr;
    reg        m_wr;
    reg        m_rd;
    reg [31:0] m_wdata;
    wire  [31:0] m_rdata;

    spi_master_top #(.MAX_W(32), .LEN_W(6), .DIV_W(8), .CNT_W(8), .N_CS(4),
                     .SEL_W(2)) u_master (
        .clk(clk), .rst_n(rst_n),
        .reg_addr(m_addr), .reg_wr(m_wr), .reg_rd(m_rd),
        .reg_wdata(m_wdata), .reg_rdata(m_rdata),
        .sclk(sclk), .mosi(mosi), .miso(miso), .cs_n(cs_n)
    );

    // --- the slave: Chapter 14.10 -------------------------------------------
    reg             s_cpol;
    reg             s_cpha;
    reg             s_lsb;
    reg [LEN_W-1:0] s_len;
    reg [MAX_W-1:0] s_default;
    reg [PTR_W-1:0] s_rd_idx;
    reg             s_tx_wr;
    reg [MAX_W-1:0] s_tx_data;
    reg             s_clr;

    wire [MAX_W-1:0] s_rd_data;
    wire [PTR_W:0]   s_rd_count;
    wire [SEQ_W-1:0] s_rd_seq;
    wire             s_tx_ready;
    wire [MAX_W-1:0] s_partial_data;
    wire [LEN_W-1:0] s_partial_bits;
    wire             s_partial_valid;
    wire             s_ratio_err, s_lead_short, s_half_short, s_gap_short;
    wire [CNT_W-1:0] s_min_half;
    wire [7:0]       s_lead_seen, s_half_seen, s_gap_seen;
    wire             s_cpol_mismatch, s_phase_suspect, s_aborted;
    wire [3:0]       s_moved_seen;
    wire             s_was_stranded, s_tx_underrun, s_rx_overflow, s_len_err;

    spi_slave_top #(.MAX_W(MAX_W), .LEN_W(LEN_W), .CNT_W(CNT_W), .SYNC_N(SYNC_N),
                    .HALF_MIN(3), .LEAD_MIN(3), .GAP_MIN(3),
                    .DEPTH(4), .PTR_W(PTR_W), .SEQ_W(SEQ_W)) u_slave (
        .clk(clk), .rst_n(rst_n),
        .cpol(s_cpol), .cpha(s_cpha), .lsb_first(s_lsb), .len(s_len),
        .tx_default(s_default),
        .sys_rd_idx(s_rd_idx), .sys_rd_data(s_rd_data),
        .sys_rd_count(s_rd_count), .sys_rd_seq(s_rd_seq),
        .sys_tx_wr(s_tx_wr), .sys_tx_data(s_tx_data),
        .sys_tx_ready(s_tx_ready),
        .partial_data(s_partial_data), .partial_bits(s_partial_bits),
        .partial_valid_stb(s_partial_valid),
        .ratio_err(s_ratio_err), .min_half(s_min_half),
        .lead_short(s_lead_short), .lead_seen(s_lead_seen),
        .half_short(s_half_short), .half_seen(s_half_seen),
        .gap_short(s_gap_short), .gap_seen(s_gap_seen),
        .cpol_mismatch(s_cpol_mismatch), .phase_suspect(s_phase_suspect),
        .moved_seen(s_moved_seen),
        .aborted(s_aborted), .was_stranded(s_was_stranded),
        .tx_underrun(s_tx_underrun), .rx_overflow(s_rx_overflow),
        .len_err(s_len_err), .clr_flags(s_clr),
        .sclk_pin(sclk), .cs_n_pin(cs_n[0]), .mosi_pin(mosi), .miso_pad(miso)
    );

    integer errors;

    // A watchdog, because a simulation that never terminates reports nothing.
    initial begin
        #4_000_000;
        $display("FAIL: the simulation did not finish within its time limit");
        $finish;
    end

        task adv;
        input integer n;
        begin repeat (n) @(negedge clk); end
    endtask

    // --- the master's software side -----------------------------------------
    reg [31:0] m_q;

        task m_write;
        input [4:0] a;
        input [31:0] d;
        begin
            @(negedge clk);
            m_addr = a; m_wdata = d; m_wr = 1'b1;
            @(negedge clk);
            m_wr = 1'b0;
        end
    endtask

        task m_read;
        input [4:0] a;
        begin
            @(negedge clk);
            m_addr = a; m_rd = 1'b1;
            #1 m_q = m_rdata;
            @(negedge clk);
            m_rd = 1'b0;
        end
    endtask

    // Programs the master. `div` is the SCLK period, so the half-period the slave sees
    // is div/2 -- which is how the master's divisor and the slave's HALF requirement meet.
        task m_configure;
        input integer div;
        input pol;
        input pha;
        input lsb;
        input integer nbits;
        input integer lead;
        input integer lag;
        input integer gap;
        begin
            m_write(A_CTRL, (pol ? 32'h1 : 32'h0) | (pha ? 32'h2 : 32'h0) |
                            (lsb ? 32'h4 : 32'h0) |
                            ((div & 32'hFF) << 8) | ((nbits & 32'h3F) << 16));
            m_write(A_TIMING, (lead & 32'hFF) | ((lag & 32'hFF) << 8) |
                              ((gap & 32'hFF) << 16));
        end
    endtask

    integer m_got_n;
    reg [31:0] m_got [0:31];

    task m_collect;
        begin
            m_read(A_STATUS);
            while (m_q[S_RXRDY]) begin
                m_read(A_RXDATA);
                m_got[m_got_n] = m_q;
                m_got_n = m_got_n + 1;
                m_read(A_STATUS);
            end
        end
    endtask

        task m_send;
        input [31:0] d;
        input last;
        integer guard;
        begin
            guard = 8000;
            m_read(A_STATUS);
            while (!m_q[S_TXRDY] && guard > 0) begin
                m_read(A_STATUS);
                guard = guard - 1;
            end
            if (guard == 0) begin
                $display("  FAIL: the master's queue never became ready");
                errors = errors + 1;
            end
            m_write(last ? A_TXLAST : A_TXDATA, d);
        end
    endtask

    task m_wait_done;
        integer guard;
        begin
            guard = 8000;
            m_read(A_STATUS);
            while (!m_q[S_BUSY] && guard > 0) begin
                m_collect();
                m_read(A_STATUS);
                guard = guard - 1;
            end
            guard = 20000;
            m_read(A_STATUS);
            while (m_q[S_BUSY] && guard > 0) begin
                m_collect();
                m_read(A_STATUS);
                guard = guard - 1;
            end
            adv(12);
            m_collect();
        end
    endtask

    // --- the slave's software side ------------------------------------------
        task s_write;
        input [MAX_W-1:0] v;
        integer guard;
        begin
            guard = 4000;
            while (!s_tx_ready && guard > 0) begin
                @(negedge clk);
                guard = guard - 1;
            end
            s_tx_data = v;
            s_tx_wr   = 1'b1;
            @(negedge clk);
            s_tx_wr   = 1'b0;
        end
    endtask

    task clear_both;
        begin
            s_clr = 1'b1; adv(1); s_clr = 1'b0;
            m_write(A_CMD, 32'h2);
            adv(2);
        end
    endtask

    integer k, bad;

    initial begin
        m_got_n = 0;

        adv(3);
        rst_n = 1'b1;
        adv(4);

        // 1. ONE BYTE, EACH WAY, WITH THE REQUIREMENTS RESPECTED. A divisor of 8 gives a
        //    half-period of 4, and a lead, lag and gap of 6 -- all comfortably above the
        //    three numbers the slave requires.
        clear_both();
        m_configure(8, 1'b0, 1'b0, 1'b0, 8, 6, 6, 6);
        s_write(32'h5A);
        m_got_n = 0;
        m_send(32'hA5, 1'b1);
        m_wait_done();

        if (s_rd_count != 1 || s_rd_data[7:0] !== 8'hA5) begin
            $display("  FAIL: the slave received %0d words, the first being %02h, expected 1 and a5",
                     s_rd_count, s_rd_data[7:0]);
            errors = errors + 1;
        end
        if (m_got_n != 1 || m_got[0][7:0] !== 8'h5A) begin
            $display("  FAIL: the master read %0d words, the first being %02h, expected 1 and 5a",
                     m_got_n, m_got[0][7:0]);
            errors = errors + 1;
        end
        $display("  the master sent a5 and the slave received a5; the slave sent 5a and the master received 5a -- two independently verified designs, wired together and unmodified");

        // 2. THE REQUIREMENTS ARE MET, AND THE SLAVE SAYS SO WITH NUMBERS -- one of
        //    which disagrees with its own flag, on purpose.
        //
        //    `min_half` reports 2 where the programmed half-period is 4, and
        //    `ratio_err` stays clear. Both are correct, and the reason is the thing
        //    this chapter exists to find.
        //
        //    Chapter 13's master ends a transaction on its final capture rather than a
        //    half-period after it, so the interval closed by the CLOSING edge is short
        //    -- measured here, and at every divisor, as exactly 2 cycles. That edge
        //    carries no data in CPHA=0: every bit has already been captured on a
        //    leading edge, and the closing edge is only SCLK returning to idle.
        //
        //    So Chapter 14.1 measures it and does not judge it. Had it judged every
        //    interval, `ratio_err` would fire on every transaction that ever worked --
        //    and a flag that is always set is a flag nobody reads. Had it not measured
        //    it, the closing edge would be invisible and this paragraph could not have
        //    been written.
        //
        //    Neither design was changed to make this pass. The front end was given the
        //    distinction it was missing, and the distinction came from the measurement.
        if (s_ratio_err || s_lead_short || s_half_short || s_gap_short) begin
            $display("  FAIL: a correctly programmed master was reported as violating a requirement: ratio=%0b lead=%0b half=%0b gap=%0b",
                     s_ratio_err, s_lead_short, s_half_short, s_gap_short);
            errors = errors + 1;
        end
        if (s_min_half != 12'd2) begin
            $display("  FAIL: the shortest interval measured %0d, expected 2 -- the master's closing edge is meant to be visible in the measurement",
                     s_min_half);
            errors = errors + 1;
        end
        $display("  the slave reports no violation, with measured values: lead %0d, half %0d, gap %0d",
                 s_lead_seen, s_half_seen, s_gap_seen);
        $display("  and the shortest interval it saw was %0d, against a programmed half-period of 4, because the master ends a transaction on its final capture and returns SCLK to idle early -- measured and deliberately not judged, since that closing edge carries no data and a flag that fired on every working transaction would be a flag nobody reads",
                 s_min_half);

        // 3. A MULTI-BYTE TRANSACTION IN BOTH DIRECTIONS, which is what a real device
        //    does -- a command byte and a response.
        clear_both();
        m_got_n = 0;
        s_write(32'hDE);
        m_send(32'h03, 1'b0);      // a flash-like READ opcode
        m_collect();
        s_write(32'hAD);
        m_send(32'h12, 1'b0);
        m_collect();
        s_write(32'hBE);
        m_send(32'h34, 1'b0);
        m_collect();
        s_write(32'hEF);
        m_send(32'h56, 1'b1);
        m_collect();
        m_wait_done();

        if (s_rd_count != 4) begin
            $display("  FAIL: a four-byte transaction gave the slave %0d words",
                     s_rd_count);
            errors = errors + 1;
        end else begin
            bad = 0;
            for (k = 0; k < 4; k = k + 1) begin
                s_rd_idx = k[PTR_W-1:0];
                adv(1);
                case (k)
                    0: if (s_rd_data[7:0] !== 8'h03) bad = bad + 1;
                    1: if (s_rd_data[7:0] !== 8'h12) bad = bad + 1;
                    2: if (s_rd_data[7:0] !== 8'h34) bad = bad + 1;
                    3: if (s_rd_data[7:0] !== 8'h56) bad = bad + 1;
                endcase
            end
            if (bad != 0) begin
                $display("  FAIL: %0d of the slave's four received bytes were wrong", bad);
                errors = errors + 1;
            end
        end
        if (m_got_n != 4 || m_got[0][7:0] !== 8'hDE || m_got[1][7:0] !== 8'hAD ||
            m_got[2][7:0] !== 8'hBE || m_got[3][7:0] !== 8'hEF) begin
            $display("  FAIL: the master read %0d bytes: %02h %02h %02h %02h",
                     m_got_n, m_got[0][7:0], m_got[1][7:0], m_got[2][7:0],
                     m_got[3][7:0]);
            errors = errors + 1;
        end
        if (s_aborted || s_tx_underrun || s_rx_overflow) begin
            $display("  FAIL: a clean four-byte transaction set a fault flag");
            errors = errors + 1;
        end
        $display("  a four-byte transaction under one chip select: the slave received 03 12 34 56 and the master received de ad be ef, with no fault flag set");

        // 4. ALL FOUR MODES, both sides configured to agree.
        for (k = 0; k < 4; k = k + 1) begin
            clear_both();
            s_cpol = k[0]; s_cpha = k[1];
            adv(2);
            m_configure(8, k[0], k[1], 1'b0, 8, 6, 6, 6);
            m_got_n = 0;
            s_write(32'h3C);
            m_send(32'hC3, 1'b1);
            m_wait_done();
            // Test 3 left the read index at the end of its sweep. `sys_rd_idx` is a
            // combinational window into the buffer, not a pointer the slave advances,
            // so a read of word 0 has to ASK for word 0.
            s_rd_idx = {PTR_W{1'b0}};
            adv(2);
            if (s_rd_data[7:0] !== 8'hC3) begin
                $display("  FAIL: mode %0d -- the slave received %02h, expected c3",
                         k, s_rd_data[7:0]);
                errors = errors + 1;
            end
            if (m_got_n != 1 || m_got[0][7:0] !== 8'h3C) begin
                $display("  FAIL: mode %0d -- the master received %02h, expected 3c",
                         k, m_got[0][7:0]);
                errors = errors + 1;
            end
            if (s_cpol_mismatch || s_phase_suspect) begin
                $display("  FAIL: mode %0d -- a matched pair reported a mode mismatch",
                         k);
                errors = errors + 1;
            end
        end
        s_cpol = 1'b0; s_cpha = 1'b0;
        adv(2);
        $display("  all four modes with both sides configured to agree: correct data both ways and no mode mismatch reported");

        // 5. NOW PROGRAM THE MASTER BADLY, ONE REQUIREMENT AT A TIME, and check the slave
        //    names the violation. This is what the measured outputs are for.
        //
        //    (a) too short a lead.
        clear_both();
        m_configure(8, 1'b0, 1'b0, 1'b0, 8, 1, 6, 6);
        s_write(32'h11);
        m_got_n = 0;
        m_send(32'h22, 1'b1);
        m_wait_done();
        if (!s_lead_short) begin
            $display("  FAIL: a lead of 1 was not reported, with a measured lead of %0d",
                     s_lead_seen);
            errors = errors + 1;
        end
        $display("  a master programmed with a lead of 1: the slave reports a short lead, measuring %0d recovered cycles",
                 s_lead_seen);

        //    (b) too fast a clock -- a divisor of 4 gives a half-period of 2, below
        //        the SYNC_N + 1 the slave needs on EVERY half-period rather than just
        //        the last one, which is a different fault from the one in test 2 and
        //        lands in a different flag.
        clear_both();
        m_configure(4, 1'b0, 1'b0, 1'b0, 8, 6, 6, 6);
        s_write(32'h33);
        m_got_n = 0;
        m_send(32'h44, 1'b1);
        m_wait_done();
        if (!s_half_short) begin
            $display("  FAIL: a half-period of 2 was not reported, measuring %0d",
                     s_half_seen);
            errors = errors + 1;
        end
        $display("  a master programmed with a divisor of 4, giving a half-period of 2: the slave reports a short half-period, measuring %0d",
                 s_half_seen);

        //    (c) a polarity mismatch -- the master idles SCLK high and the slave expects
        //        it low. Caught before a single bit moves.
        clear_both();
        m_configure(8, 1'b1, 1'b0, 1'b0, 8, 6, 6, 6);   // master CPOL=1
        s_cpol = 1'b0;                                   // slave still CPOL=0
        adv(2);
        s_write(32'h55);
        m_got_n = 0;
        m_send(32'h66, 1'b1);
        m_wait_done();
        if (!s_cpol_mismatch) begin
            $display("  FAIL: a polarity mismatch between the two designs was not reported");
            errors = errors + 1;
        end
        $display("  the master programmed CPOL=1 against a slave configured CPOL=0: the slave reports a polarity mismatch");

        //    (d) a phase mismatch. This is the one that cannot be found by counting,
        //        and the integration is where that matters most: the master here sends
        //        a whole number of frames, the slave agrees it received a whole number
        //        of frames, and the transaction is still wrong. What gives it away is
        //        that MOSI is in motion at the instant the slave samples it.
        clear_both();
        s_cpol = 1'b0; s_cpha = 1'b0;
        adv(2);
        m_configure(8, 1'b0, 1'b1, 1'b0, 8, 6, 6, 6);   // master CPHA=1
        s_write(32'h77);
        m_got_n = 0;
        m_send(32'h88, 1'b1);                            // 0x88 has transitions in it
        m_wait_done();
        if (!s_phase_suspect) begin
            $display("  FAIL: a phase-mismatched master was not diagnosed, with %0d motion events",
                     s_moved_seen);
            errors = errors + 1;
        end
        if (s_aborted) begin
            $display("  FAIL: a phase mismatch was reported as an abort, which would send the diagnosis to the wrong place");
            errors = errors + 1;
        end
        $display("  the master programmed CPHA=1 against a slave configured CPHA=0: diagnosed inside ONE transaction, from MOSI being in motion at %0d of the captures -- and the transaction was NOT flagged as an abort, because the master sent a whole number of frames and every edge arrived",
                 s_moved_seen);

        // 6. AND BACK TO A GOOD CONFIGURATION, to show the system recovers -- a
        //    misconfiguration is a diagnosis, not damage.
        clear_both();
        s_cpol = 1'b0; s_cpha = 1'b0;
        adv(2);
        m_configure(8, 1'b0, 1'b0, 1'b0, 8, 6, 6, 6);
        s_write(32'h99);
        m_got_n = 0;
        m_send(32'hAA, 1'b1);
        m_wait_done();
        if (s_rd_data[7:0] !== 8'hAA || m_got_n != 1 || m_got[0][7:0] !== 8'h99) begin
            $display("  FAIL: after four misconfigurations the system did not recover: slave got %02h, master got %02h",
                     s_rd_data[7:0], m_got[0][7:0]);
            errors = errors + 1;
        end
        if (s_ratio_err || s_lead_short || s_half_short || s_cpol_mismatch ||
            s_phase_suspect || s_aborted) begin
            $display("  FAIL: a good configuration after four bad ones still reports a fault");
            errors = errors + 1;
        end
        $display("  a good configuration after four bad ones works immediately and reports nothing: a misconfiguration is a diagnosis rather than damage");

        if (errors == 0)
            $display("PASS: the master of Chapter 13.11 and the slave of Chapter 14.10 are wired together unmodified, each verified independently against pin-level requirements rather than against the other, and they interoperate on the first attempt -- a byte each way, a four-byte transaction under one chip select in both directions, and all four modes with both sides agreeing, with no fault flag set anywhere -- because Chapter 14 derived three numbers a master must respect and Chapter 13 made all three programmable, so integration is a matter of writing the right values into the master's timing register -- and integration also found the one thing neither bench could have found on its own, that this master returns SCLK to idle two cycles after its final capture at every divisor, which is why Chapter 14.1 measures the closing interval and deliberately declines to judge it: the closing edge carries no data, and a flag that fired on every transaction that ever worked would be a flag nobody reads -- and when the master is programmed BADLY the slave names which requirement was violated: a lead of one is reported as a short lead with the measured interval, a divisor of four is reported as a short half-period, a polarity disagreement is caught before a bit has moved, and a phase disagreement -- which sends a whole number of frames and so cannot be found by counting anything -- is diagnosed inside a single transaction from MOSI being in motion within one cycle of the moment the slave samples it -- after which a good configuration works immediately, because a misconfiguration is a diagnosis and not damage");
        else
            $display("FAIL: %0d error(s)", errors);
        $finish;
    end


    initial begin
        clk = 1'b0;
        rst_n = 1'b0;
        m_addr = 5'h0;
        m_wr = 1'b0;
        m_rd = 1'b0;
        m_wdata = 32'h0;
        s_cpol = 1'b0;
        s_cpha = 1'b0;
        s_lsb = 1'b0;
        s_len = 6'd8;
        s_default = 32'hFF;
        s_rd_idx = 2'd0;
        s_tx_wr = 1'b0;
        s_tx_data = 32'h0;
        s_clr = 1'b0;
        errors = 0;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slave_top_tb.vhd — the same bench in VHDL
-- spi_slave_top_tb.vhd
--
-- The master in this testbench is not a model. It is `spi_master_top` from Chapter 13.11,
-- byte for byte -- the design that module built and verified -- driven through its own
-- register interface exactly as software would drive it.
--
-- That is the point of the test, and it is the strongest thing either module can say. Two
-- designs built and verified independently, against pin-level requirements rather than
-- against each other, now wired together with nothing in between:
--
--     master.sclk  -> slave.sclk_pin
--     master.mosi  -> slave.mosi_pin
--     master.cs_n0 -> slave.cs_n_pin
--     slave.miso   -> master.miso
--
-- Neither has been adjusted to suit the other. What makes them work is that Chapter 14
-- derived three numbers the master has to respect and Chapter 13 made all three
-- programmable -- so the integration is a matter of writing the right values into the
-- master's TIMING register, and the slave reports whether they were right.
--
-- The test then does the thing a system does: it PROGRAMS THE MASTER BADLY on purpose and
-- checks the slave says which requirement was violated.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_slave_top_tb is
end entity;

architecture sim of spi_slave_top_tb is

    constant MAX_W  : positive := 32;
    constant LEN_W  : positive := 6;
    constant CNT_W  : positive := 12;
    constant SYNC_N : positive := 2;
    constant PTR_W  : positive := 2;
    constant SEQ_W  : positive := 8;

    -- The master's register map, from Chapter 13.11.
    constant A_CTRL   : unsigned(4 downto 0) := to_unsigned(16#00#, 5);
    constant A_TIMING : unsigned(4 downto 0) := to_unsigned(16#04#, 5);
    constant A_TXDATA : unsigned(4 downto 0) := to_unsigned(16#08#, 5);
    constant A_TXLAST : unsigned(4 downto 0) := to_unsigned(16#0C#, 5);
    constant A_RXDATA : unsigned(4 downto 0) := to_unsigned(16#10#, 5);
    constant A_STATUS : unsigned(4 downto 0) := to_unsigned(16#14#, 5);
    constant A_CMD    : unsigned(4 downto 0) := to_unsigned(16#18#, 5);

    constant S_TXRDY : natural := 0;
    constant S_RXRDY : natural := 1;
    constant S_BUSY  : natural := 2;

    signal clk   : std_logic := '0';
    signal rst_n : std_logic := '0';
    signal halt  : boolean   := false;

    -- the shared pins
    signal sclk, mosi, miso : std_logic;
    signal cs_n : std_logic_vector(3 downto 0);

    -- the master's register interface
    signal m_addr  : unsigned(4 downto 0) := (others => '0');
    signal m_wr    : std_logic := '0';
    signal m_rd    : std_logic := '0';
    signal m_wdata : std_logic_vector(31 downto 0) := (others => '0');
    signal m_rdata : std_logic_vector(31 downto 0);

    -- the slave's configuration and system side
    signal s_cpol    : std_logic := '0';
    signal s_cpha    : std_logic := '0';
    signal s_lsb     : std_logic := '0';
    signal s_len     : unsigned(LEN_W - 1 downto 0) := to_unsigned(8, LEN_W);
    signal s_default : std_logic_vector(MAX_W - 1 downto 0) := x"000000FF";
    signal s_rd_idx  : unsigned(PTR_W - 1 downto 0) := (others => '0');
    signal s_tx_wr   : std_logic := '0';
    signal s_tx_data : std_logic_vector(MAX_W - 1 downto 0) := (others => '0');
    signal s_clr     : std_logic := '0';

    signal s_rd_data  : std_logic_vector(MAX_W - 1 downto 0);
    signal s_rd_count : unsigned(PTR_W downto 0);
    signal s_rd_seq   : unsigned(SEQ_W - 1 downto 0);
    signal s_tx_ready : std_logic;

    signal s_partial_data  : std_logic_vector(MAX_W - 1 downto 0);
    signal s_partial_bits  : unsigned(LEN_W - 1 downto 0);
    signal s_partial_valid : std_logic;

    signal s_ratio_err, s_lead_short, s_half_short, s_gap_short : std_logic;
    signal s_min_half : unsigned(CNT_W - 1 downto 0);
    signal s_lead_seen, s_half_seen, s_gap_seen : unsigned(7 downto 0);
    signal s_cpol_mismatch, s_phase_suspect, s_aborted : std_logic;
    signal s_moved_seen : unsigned(3 downto 0);
    signal s_was_stranded, s_tx_underrun, s_rx_overflow, s_len_err : std_logic;

begin

    clkgen : process
    begin
        while not halt loop
            clk <= '0'; wait for 5 ns;
            clk <= '1'; wait for 5 ns;
        end loop;
        wait;
    end process;

    -- --- the master: Chapter 13.11, unmodified ----------------------------------
    u_master : entity work.spi_master_top
        generic map (MAX_W => 32, LEN_W => 6, DIV_W => 8, CNT_W => 8,
                     N_CS => 4, SEL_W => 2)
        port map (clk => clk, rst_n => rst_n,
                  reg_addr => m_addr, reg_wr => m_wr, reg_rd => m_rd,
                  reg_wdata => m_wdata, reg_rdata => m_rdata,
                  sclk => sclk, mosi => mosi, miso => miso, cs_n => cs_n);

    -- --- the slave: Chapter 14.10 -----------------------------------------------
    u_slave : entity work.spi_slave_top
        generic map (MAX_W => MAX_W, LEN_W => LEN_W, CNT_W => CNT_W,
                     SYNC_N => SYNC_N, HALF_MIN => 3, LEAD_MIN => 3, GAP_MIN => 3,
                     DEPTH => 4, PTR_W => PTR_W, SEQ_W => SEQ_W)
        port map (clk => clk, rst_n => rst_n,
                  cpol => s_cpol, cpha => s_cpha, lsb_first => s_lsb,
                  len => s_len, tx_default => s_default,
                  sys_rd_idx => s_rd_idx, sys_rd_data => s_rd_data,
                  sys_rd_count => s_rd_count, sys_rd_seq => s_rd_seq,
                  sys_tx_wr => s_tx_wr, sys_tx_data => s_tx_data,
                  sys_tx_ready => s_tx_ready,
                  partial_data => s_partial_data, partial_bits => s_partial_bits,
                  partial_valid_stb => s_partial_valid,
                  ratio_err => s_ratio_err, min_half => s_min_half,
                  lead_short => s_lead_short, lead_seen => s_lead_seen,
                  half_short => s_half_short, half_seen => s_half_seen,
                  gap_short => s_gap_short, gap_seen => s_gap_seen,
                  cpol_mismatch => s_cpol_mismatch,
                  phase_suspect => s_phase_suspect, moved_seen => s_moved_seen,
                  aborted => s_aborted, was_stranded => s_was_stranded,
                  tx_underrun => s_tx_underrun, rx_overflow => s_rx_overflow,
                  len_err => s_len_err, clr_flags => s_clr,
                  sclk_pin => sclk, cs_n_pin => cs_n(0), mosi_pin => mosi,
                  miso_pad => miso);

    -- A watchdog, because a simulation that never terminates reports nothing. The
    -- `halt` guard matters: stopping the clock does not stop simulation TIME, so a
    -- watchdog without it fires after the test has already finished.
    watchdog : process
    begin
        wait for 4 ms;
        if not halt then
            report "FAIL: the simulation did not finish within its time limit"
                severity failure;
        end if;
        wait;
    end process;

    stim : process
        variable errs  : natural := 0;
        variable m_q   : std_logic_vector(31 downto 0) := (others => '0');
        variable m_got : std_logic_vector(31 downto 0);
        type word_arr is array (0 to 31) of std_logic_vector(31 downto 0);
        variable got   : word_arr := (others => (others => '0'));
        variable got_n : natural := 0;
        variable guard : natural;
        variable bad   : natural;

        procedure adv(n : natural) is
        begin
            for j in 1 to n loop wait until falling_edge(clk); end loop;
        end procedure;

        procedure m_write(a : unsigned(4 downto 0);
                          d : std_logic_vector(31 downto 0)) is
        begin
            wait until falling_edge(clk);
            m_addr  <= a;
            m_wdata <= d;
            m_wr    <= '1';
            wait until falling_edge(clk);
            m_wr    <= '0';
        end procedure;

        -- The read samples `reg_rdata` a delta-plus after driving the address, exactly
        -- as the SystemVerilog bench does, because the master's read path is
        -- combinational from the address.
        procedure m_read(a : unsigned(4 downto 0)) is
        begin
            wait until falling_edge(clk);
            m_addr <= a;
            m_rd   <= '1';
            wait for 1 ns;
            m_q    := m_rdata;
            wait until falling_edge(clk);
            m_rd   <= '0';
        end procedure;

        -- Programs the master. `div` is the SCLK period, so the half-period the slave
        -- sees is div/2 -- which is how the master's divisor and the slave's HALF
        -- requirement meet.
        procedure m_configure(div : natural; pol : std_logic; pha : std_logic;
                              lsb : std_logic; nbits : natural;
                              lead : natural; lag : natural; gap : natural) is
            variable w : unsigned(31 downto 0);
        begin
            w := (others => '0');
            if pol = '1' then w(0) := '1'; end if;
            if pha = '1' then w(1) := '1'; end if;
            if lsb = '1' then w(2) := '1'; end if;
            w(15 downto 8)  := to_unsigned(div, 8);
            w(21 downto 16) := to_unsigned(nbits, 6);
            m_write(A_CTRL, std_logic_vector(w));

            w := (others => '0');
            w(7 downto 0)   := to_unsigned(lead, 8);
            w(15 downto 8)  := to_unsigned(lag, 8);
            w(23 downto 16) := to_unsigned(gap, 8);
            m_write(A_TIMING, std_logic_vector(w));
        end procedure;

        procedure m_collect is
        begin
            m_read(A_STATUS);
            while m_q(S_RXRDY) = '1' loop
                m_read(A_RXDATA);
                got(got_n) := m_q;
                got_n      := got_n + 1;
                m_read(A_STATUS);
            end loop;
        end procedure;

        procedure m_send(d : std_logic_vector(31 downto 0); last : boolean) is
        begin
            guard := 8000;
            m_read(A_STATUS);
            while m_q(S_TXRDY) = '0' and guard > 0 loop
                m_read(A_STATUS);
                guard := guard - 1;
            end loop;
            if guard = 0 then
                report "  FAIL: the master's queue never became ready";
                errs := errs + 1;
            end if;
            if last then
                m_write(A_TXLAST, d);
            else
                m_write(A_TXDATA, d);
            end if;
        end procedure;

        procedure m_wait_done is
        begin
            guard := 8000;
            m_read(A_STATUS);
            while m_q(S_BUSY) = '0' and guard > 0 loop
                m_collect;
                m_read(A_STATUS);
                guard := guard - 1;
            end loop;
            guard := 20000;
            m_read(A_STATUS);
            while m_q(S_BUSY) = '1' and guard > 0 loop
                m_collect;
                m_read(A_STATUS);
                guard := guard - 1;
            end loop;
            adv(12);
            m_collect;
        end procedure;

        procedure s_write(v : std_logic_vector(MAX_W - 1 downto 0)) is
        begin
            guard := 4000;
            while s_tx_ready = '0' and guard > 0 loop
                wait until falling_edge(clk);
                guard := guard - 1;
            end loop;
            s_tx_data <= v;
            s_tx_wr   <= '1';
            wait until falling_edge(clk);
            s_tx_wr   <= '0';
        end procedure;

        procedure clear_both is
        begin
            s_clr <= '1'; adv(1); s_clr <= '0';
            m_write(A_CMD, x"00000002");
            adv(2);
        end procedure;
    begin
        got_n := 0;

        adv(3);
        rst_n <= '1';
        adv(4);

        -- 1. ONE BYTE, EACH WAY, WITH THE REQUIREMENTS RESPECTED. A divisor of 8 gives
        --    a half-period of 4, and a lead, lag and gap of 6 -- all comfortably above
        --    the three numbers the slave requires.
        clear_both;
        m_configure(8, '0', '0', '0', 8, 6, 6, 6);
        s_write(x"0000005A");
        got_n := 0;
        m_send(x"000000A5", true);
        m_wait_done;

        if to_integer(s_rd_count) /= 1 or s_rd_data(7 downto 0) /= x"A5" then
            report "  FAIL: the slave received " &
                   integer'image(to_integer(s_rd_count)) & " words, the first being " &
                   integer'image(to_integer(unsigned(s_rd_data(7 downto 0)))) &
                   ", expected 1 and a5";
            errs := errs + 1;
        end if;
        if got_n /= 1 or got(0)(7 downto 0) /= x"5A" then
            report "  FAIL: the master read " & integer'image(got_n) &
                   " words, the first being " &
                   integer'image(to_integer(unsigned(got(0)(7 downto 0)))) &
                   ", expected 1 and 5a";
            errs := errs + 1;
        end if;
        report "  the master sent a5 and the slave received a5; the slave sent 5a and the master received 5a -- two independently verified designs, wired together and unmodified";

        -- 2. THE REQUIREMENTS ARE MET, AND THE SLAVE SAYS SO WITH NUMBERS -- one of
        --    which disagrees with its own flag, on purpose.
        --
        --    `min_half` reports 2 where the programmed half-period is 4, and
        --    `ratio_err` stays clear. Both are correct, and the reason is the thing
        --    this chapter exists to find.
        --
        --    Chapter 13's master ends a transaction on its final capture rather than a
        --    half-period after it, so the interval closed by the CLOSING edge is short
        --    -- measured here, and at every divisor, as exactly 2 cycles. That edge
        --    carries no data in CPHA=0: every bit has already been captured on a
        --    leading edge, and the closing edge is only SCLK returning to idle.
        --
        --    So Chapter 14.1 measures it and does not judge it. Had it judged every
        --    interval, `ratio_err` would fire on every transaction that ever worked --
        --    and a flag that is always set is a flag nobody reads. Had it not measured
        --    it, the closing edge would be invisible and this paragraph could not have
        --    been written.
        --
        --    Neither design was changed to make this pass. The front end was given the
        --    distinction it was missing, and the distinction came from the measurement.
        if s_ratio_err = '1' or s_lead_short = '1' or s_half_short = '1' or
           s_gap_short = '1' then
            report "  FAIL: a correctly programmed master was reported as violating a requirement";
            errs := errs + 1;
        end if;
        if to_integer(s_min_half) /= 2 then
            report "  FAIL: the shortest interval measured " &
                   integer'image(to_integer(s_min_half)) &
                   ", expected 2 -- the master's closing edge is meant to be visible in the measurement";
            errs := errs + 1;
        end if;
        report "  the slave reports no violation, with measured values: lead " &
               integer'image(to_integer(s_lead_seen)) & ", half " &
               integer'image(to_integer(s_half_seen)) & ", gap " &
               integer'image(to_integer(s_gap_seen));
        report "  and the shortest interval it saw was " &
               integer'image(to_integer(s_min_half)) &
               ", against a programmed half-period of 4, because the master ends a transaction on its final capture and returns SCLK to idle early -- measured and deliberately not judged, since that closing edge carries no data and a flag that fired on every working transaction would be a flag nobody reads";

        -- 3. A MULTI-BYTE TRANSACTION IN BOTH DIRECTIONS, which is what a real device
        --    does -- a command byte and a response.
        clear_both;
        got_n := 0;
        s_write(x"000000DE");
        m_send(x"00000003", false);      -- a flash-like READ opcode
        m_collect;
        s_write(x"000000AD");
        m_send(x"00000012", false);
        m_collect;
        s_write(x"000000BE");
        m_send(x"00000034", false);
        m_collect;
        s_write(x"000000EF");
        m_send(x"00000056", true);
        m_collect;
        m_wait_done;

        if to_integer(s_rd_count) /= 4 then
            report "  FAIL: a four-byte transaction gave the slave " &
                   integer'image(to_integer(s_rd_count)) & " words";
            errs := errs + 1;
        else
            bad := 0;
            for kk in 0 to 3 loop
                s_rd_idx <= to_unsigned(kk, PTR_W);
                adv(1);
                case kk is
                    when 0 => if s_rd_data(7 downto 0) /= x"03" then bad := bad + 1; end if;
                    when 1 => if s_rd_data(7 downto 0) /= x"12" then bad := bad + 1; end if;
                    when 2 => if s_rd_data(7 downto 0) /= x"34" then bad := bad + 1; end if;
                    when others => if s_rd_data(7 downto 0) /= x"56" then bad := bad + 1; end if;
                end case;
            end loop;
            if bad /= 0 then
                report "  FAIL: " & integer'image(bad) &
                       " of the slave's four received bytes were wrong";
                errs := errs + 1;
            end if;
        end if;
        if got_n /= 4 or got(0)(7 downto 0) /= x"DE" or got(1)(7 downto 0) /= x"AD" or
           got(2)(7 downto 0) /= x"BE" or got(3)(7 downto 0) /= x"EF" then
            report "  FAIL: the master read " & integer'image(got_n) &
                   " bytes and they were not de ad be ef";
            errs := errs + 1;
        end if;
        if s_aborted = '1' or s_tx_underrun = '1' or s_rx_overflow = '1' then
            report "  FAIL: a clean four-byte transaction set a fault flag";
            errs := errs + 1;
        end if;
        report "  a four-byte transaction under one chip select: the slave received 03 12 34 56 and the master received de ad be ef, with no fault flag set";

        -- 4. ALL FOUR MODES, both sides configured to agree.
        for kk in 0 to 3 loop
            clear_both;
            if (kk mod 2) = 1 then s_cpol <= '1'; else s_cpol <= '0'; end if;
            if kk >= 2       then s_cpha <= '1'; else s_cpha <= '0'; end if;
            adv(2);
            if (kk mod 2) = 1 then
                if kk >= 2 then m_configure(8, '1', '1', '0', 8, 6, 6, 6);
                else            m_configure(8, '1', '0', '0', 8, 6, 6, 6); end if;
            else
                if kk >= 2 then m_configure(8, '0', '1', '0', 8, 6, 6, 6);
                else            m_configure(8, '0', '0', '0', 8, 6, 6, 6); end if;
            end if;
            got_n := 0;
            s_write(x"0000003C");
            m_send(x"000000C3", true);
            m_wait_done;
            -- Test 3 left the read index at the end of its sweep. `sys_rd_idx` is a
            -- combinational window into the buffer, not a pointer the slave advances,
            -- so a read of word 0 has to ASK for word 0.
            s_rd_idx <= (others => '0');
            adv(2);
            if s_rd_data(7 downto 0) /= x"C3" then
                report "  FAIL: mode " & integer'image(kk) &
                       " -- the slave received the wrong byte, expected c3";
                errs := errs + 1;
            end if;
            if got_n /= 1 or got(0)(7 downto 0) /= x"3C" then
                report "  FAIL: mode " & integer'image(kk) &
                       " -- the master received the wrong byte, expected 3c";
                errs := errs + 1;
            end if;
            if s_cpol_mismatch = '1' or s_phase_suspect = '1' then
                report "  FAIL: mode " & integer'image(kk) &
                       " -- a matched pair reported a mode mismatch";
                errs := errs + 1;
            end if;
        end loop;
        s_cpol <= '0'; s_cpha <= '0';
        adv(2);
        report "  all four modes with both sides configured to agree: correct data both ways and no mode mismatch reported";

        -- 5. NOW PROGRAM THE MASTER BADLY, ONE REQUIREMENT AT A TIME, and check the
        --    slave names the violation. This is what the measured outputs are for.
        --
        --    (a) too short a lead.
        clear_both;
        m_configure(8, '0', '0', '0', 8, 1, 6, 6);
        s_write(x"00000011");
        got_n := 0;
        m_send(x"00000022", true);
        m_wait_done;
        if s_lead_short /= '1' then
            report "  FAIL: a lead of 1 was not reported, with a measured lead of " &
                   integer'image(to_integer(s_lead_seen));
            errs := errs + 1;
        end if;
        report "  a master programmed with a lead of 1: the slave reports a short lead, measuring " &
               integer'image(to_integer(s_lead_seen)) & " recovered cycles";

        --    (b) too fast a clock -- a divisor of 4 gives a half-period of 2, below
        --        the SYNC_N + 1 the slave needs on EVERY half-period rather than just
        --        the last one, which is a different fault from the one in test 2 and
        --        lands in a different flag.
        clear_both;
        m_configure(4, '0', '0', '0', 8, 6, 6, 6);
        s_write(x"00000033");
        got_n := 0;
        m_send(x"00000044", true);
        m_wait_done;
        if s_half_short /= '1' then
            report "  FAIL: a half-period of 2 was not reported, measuring " &
                   integer'image(to_integer(s_half_seen));
            errs := errs + 1;
        end if;
        report "  a master programmed with a divisor of 4, giving a half-period of 2: the slave reports a short half-period, measuring " &
               integer'image(to_integer(s_half_seen));

        --    (c) a polarity mismatch -- the master idles SCLK high and the slave
        --        expects it low. Caught before a single bit moves.
        clear_both;
        m_configure(8, '1', '0', '0', 8, 6, 6, 6);   -- master CPOL=1
        s_cpol <= '0';                                -- slave still CPOL=0
        adv(2);
        s_write(x"00000055");
        got_n := 0;
        m_send(x"00000066", true);
        m_wait_done;
        if s_cpol_mismatch /= '1' then
            report "  FAIL: a polarity mismatch between the two designs was not reported";
            errs := errs + 1;
        end if;
        report "  the master programmed CPOL=1 against a slave configured CPOL=0: the slave reports a polarity mismatch";

        --    (d) a phase mismatch. This is the one that cannot be found by counting,
        --        and the integration is where that matters most: the master here sends
        --        a whole number of frames, the slave agrees it received a whole number
        --        of frames, and the transaction is still wrong. What gives it away is
        --        that MOSI is in motion within one cycle of the instant the slave
        --        samples it.
        clear_both;
        s_cpol <= '0'; s_cpha <= '0';
        adv(2);
        m_configure(8, '0', '1', '0', 8, 6, 6, 6);   -- master CPHA=1
        s_write(x"00000077");
        got_n := 0;
        m_send(x"00000088", true);                    -- 0x88 has transitions in it
        m_wait_done;
        if s_phase_suspect /= '1' then
            report "  FAIL: a phase-mismatched master was not diagnosed, with " &
                   integer'image(to_integer(s_moved_seen)) & " motion events";
            errs := errs + 1;
        end if;
        if s_aborted = '1' then
            report "  FAIL: a phase mismatch was reported as an abort, which would send the diagnosis to the wrong place";
            errs := errs + 1;
        end if;
        report "  the master programmed CPHA=1 against a slave configured CPHA=0: diagnosed inside ONE transaction, from MOSI being in motion at " &
               integer'image(to_integer(s_moved_seen)) &
               " of the captures -- and the transaction was NOT flagged as an abort, because the master sent a whole number of frames and every edge arrived";

        -- 6. AND BACK TO A GOOD CONFIGURATION, to show the system recovers -- a
        --    misconfiguration is a diagnosis, not damage.
        clear_both;
        s_cpol <= '0'; s_cpha <= '0';
        adv(2);
        m_configure(8, '0', '0', '0', 8, 6, 6, 6);
        s_write(x"00000099");
        got_n := 0;
        m_send(x"000000AA", true);
        m_wait_done;
        if s_rd_data(7 downto 0) /= x"AA" or got_n /= 1 or
           got(0)(7 downto 0) /= x"99" then
            report "  FAIL: after four misconfigurations the system did not recover";
            errs := errs + 1;
        end if;
        if s_ratio_err = '1' or s_lead_short = '1' or s_half_short = '1' or
           s_cpol_mismatch = '1' or s_phase_suspect = '1' or s_aborted = '1' then
            report "  FAIL: a good configuration after four bad ones still reports a fault";
            errs := errs + 1;
        end if;
        report "  a good configuration after four bad ones works immediately and reports nothing: a misconfiguration is a diagnosis rather than damage";

        if errs = 0 then
            report "PASS: the master of Chapter 13.11 and the slave of Chapter 14.10 are wired together unmodified, each verified independently against pin-level requirements rather than against the other, and they interoperate on the first attempt -- a byte each way, a four-byte transaction under one chip select in both directions, and all four modes with both sides agreeing, with no fault flag set anywhere -- because Chapter 14 derived three numbers a master must respect and Chapter 13 made all three programmable, so integration is a matter of writing the right values into the master's timing register -- and integration also found the one thing neither bench could have found on its own, that this master returns SCLK to idle two cycles after its final capture at every divisor, which is why Chapter 14.1 measures the closing interval and deliberately declines to judge it: the closing edge carries no data, and a flag that fired on every transaction that ever worked would be a flag nobody reads -- and when the master is programmed BADLY the slave names which requirement was violated: a lead of one is reported as a short lead with the measured interval, a divisor of four is reported as a short half-period, a polarity disagreement is caught before a bit has moved, and a phase disagreement -- which sends a whole number of frames and so cannot be found by counting anything -- is diagnosed inside a single transaction from MOSI being in motion within one cycle of the moment the slave samples it -- after which a good configuration works immediately, because a misconfiguration is a diagnosis and not damage";
        else
            report "FAIL: " & integer'image(errs) & " error(s)" severity error;
        end if;

        halt <= true;
        wait;
    end process;

end architecture;

8. Why a Verification Engineer Cares

The strongest test of a block is another block that was not built to match it. Both of this chapter's findings came from connecting two independently verified designs, and neither could have come from anywhere else. That is an argument for integration testing that is not about coverage: a unit bench encodes its author's model of the environment, and only a second design written by someone else's reasoning can falsify that model.

Test 5 is the shape worth stealing: misconfigure deliberately, one requirement at a time, and check the diagnosis names the right one. It is not enough that a flag fires. A slave that reported ratio_err for a short lead would be useless, because the integrator would change the divisor and the fault would persist. One fault, one flag, and the bench checks the correspondence.

Test 2 asserts that a flag is clear while its own measurement is out of range, which looks like a contradiction and is the chapter's most useful assertion. It locks down §3's decision so that a future contributor who "fixes" the apparent inconsistency is told immediately.

Test 6 exists because recovery is a property that unit benches never test. Every one of the four misconfigurations leaves sticky flags and mid-transaction state. That the next good transaction works immediately is the property that distinguishes a diagnosis from damage, and it is the receive-side twin of the discipline from Chapter 14.7: always check the transaction after the interesting one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Top-level properties. These are the ones that can only be written here,
// because each spans two blocks.

property p_gate_before_detector;
    // Item 7 of the checklist, as a property: the detector never sees a
    // transaction the gate refused. If this can fail, the ordering is wrong and
    // every downstream block has a case nobody wrote.
    @(posedge clk) disable iff (!rst_n)
        !g_cs_active |-> !txn_active;
endproperty

property p_oe_independent_of_gate;
    // Item 8: a stranded slave still releases MISO on time. The enable follows
    // the UNGATED select, so it is unaffected by the gate's state.
    @(posedge clk) disable iff (!rst_n)
        !cs_active |=> !oe;
endproperty

property p_ratio_flag_excludes_closing;
    // Section 3's decision, stated so it cannot be quietly undone: a short
    // interval that was NOT followed by another edge does not set the flag.
    @(posedge clk) disable iff (!rst_n)
        (cs_deassert_stb && min_half < HALF_MIN) |-> !$rose(ratio_err);
endproperty

property p_one_fault_one_flag;
    // Test 5's correspondence, as a property over a transaction: a short lead
    // sets lead_short and nothing else. Bound per-fault in the bench rather
    // than written once, because "nothing else" needs the fault named.
    @(posedge clk) disable iff (!rst_n)
        $rose(lead_short) |-> (!half_short && !gap_short && !ratio_err);
endproperty

property p_recovery_after_clear;
    // Test 6: a clear leaves the slave able to run a clean transaction. Sticky
    // flags are sticky until cleared and no further.
    @(posedge clk) disable iff (!rst_n)
        clr_flags |=> (!ratio_err && !lead_short && !half_short &&
                       !cpol_mismatch && !phase_suspect && !aborted);
endproperty

property p_phase_fault_not_an_abort;
    // A phase mismatch must not land in `aborted`, because that would send the
    // investigation to Chapter 14.7 for a fault that is Chapter 14.6's.
    @(posedge clk) disable iff (!rst_n)
        $rose(phase_suspect) |-> !aborted;
endproperty
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Coverage at the top level. The axis is WHICH REQUIREMENT WAS VIOLATED crossed
// with the mode, because a diagnosis that is right in mode 0 and wrong in mode 3
// is a diagnosis nobody can rely on.

covergroup cg_slave_top @(posedge clk iff txn_report_stb);
    option.per_instance = 1;

    // One bin per requirement, plus the all-clear. The bins are not independent
    // -- a badly programmed master can violate two at once -- so this is a
    // coverpoint on the violation VECTOR rather than on each flag.
    violation: coverpoint {ratio_err, lead_short, half_short, gap_short,
                           cpol_mismatch, phase_suspect} {
        bins none        = {6'b000000};
        bins lead_only   = {6'b010000};
        bins half_only   = {6'b001000};
        bins gap_only    = {6'b000100};
        bins cpol_only   = {6'b000010};
        bins phase_only  = {6'b000001};
        bins ratio_only  = {6'b100000};
        bins multiple[]  = default;
    }

    mode: coverpoint {cpol, cpha} {
        bins m0 = {2'b00}; bins m1 = {2'b01};
        bins m2 = {2'b10}; bins m3 = {2'b11};
    }

    // Words per transaction. One is the common case and four exercises the
    // buffer, the word boundary and the transmit reload.
    words: coverpoint frames_in_txn {
        bins one   = {1};
        bins two   = {2};
        bins burst = {[3:4]};
        bins over  = {[5:$]};      // beyond DEPTH: overflow
    }

    x_violation_mode:  cross violation, mode;
    x_words_mode:      cross words, mode;

endgroup

9. Why an FPGA or ASIC Engineer Cares

SCLK is data, and the constraints file is where that becomes true. No create_clock on SCLK, and set_input_delay on all three inputs referenced to the system clock. A design review that finds neither has found the real problem — the RTL is correct and unconstrained, which passes every tool and fails on a board.

The three synchroniser chains need vendor attributes, and the attribute does two jobs: it stops the tool retiming or SRL-inferring a chain whose depth is the thing the design's correctness rests on, and it tells timing analysis to treat the first flop's input as an asynchronous arrival rather than reporting a violated setup path forever.

The tri-state must be at the top level driving a port. On an FPGA that is what infers an OBUFT; buried in a submodule it becomes a mux with a constant, or an error, depending on the tool. That is why Chapter 14.5's block is instantiated here with its output going straight out.

Nothing in this design is timing-critical at any plausible SCLK, and it is worth knowing where the widest paths are anyway: Chapter 14.3's reversal (MAX_W muxes) and Chapter 14.4's aligning barrel shifter. Both run once per word or once per transaction with a full system clock available.

The whole design is one clock domain, which is the payoff from Chapter 14.1's architecture choice and the thing that would change completely under architecture A. Chapter 15.4 builds that comparison; from here, the only crossing in the design is the three synchronisers at the pins.

10. Failure Signature — Two Devices That Both Pass Their Own Tests

The symptom:

"Our slave passes its full regression. The customer's master passes theirs. Together they exchange plausible wrong data, and neither side reports anything."

What is happening: one of the two findings in this chapter, or something with the same shape. A unit bench encodes its author's model of the other device, and where the two models differ the benches agree with their own designs and disagree with reality.

The two specific instances here:

  • The master's closing edge is 2 cycles rather than a half-period. The slave's bench never produced one, because its master model returned SCLK to idle after a full half-period. Symptom: a fault flag on every working transaction.
  • The master's MOSI is one cycle late. The slave's bench modelled it as changing on the edge. Symptom: a diagnostic that fires in simulation and never on hardware.

How to find this class of fault without a customer: connect the design to something written by different reasoning. A competitor's model, an open-source implementation, a colleague's independent design, or — as here — the other half of a two-module sequence written to a stated interface rather than to each other. And read every measured value the two devices publish, not just their flags: §3's finding announced itself as a number disagreeing with a flag, which is exactly the kind of thing a flag-only design cannot say.

11. Common Misconceptions

"Two designs that each pass their own regression will interoperate." They will interoperate wherever their authors' models of each other agree, and nowhere else. Both of this chapter's findings live exactly in that gap.

"The master truncating its last half-period is a master bug." It is not: that edge carries no data under CPHA=0. What it is, is a legal behaviour the slave's monitor had not accounted for — so the fix was in the monitor's judgement, not in either design.

"A flag and its own measurement should never disagree." They should, whenever the flag is a judgement and the number is an observation. ratio_err clear with min_half below the minimum is the correct report for this master, and a design that forced them to agree would either fire on every transaction or hide the measurement.

"The safe-idle gate can go anywhere, since it only blocks things." It must be before the transaction detector, because the detector has no way to un-start a transaction. Ordering is the one real decision in an otherwise structural file.

"The output enable should use the gated select for consistency." Consistency would cost a stranded slave a late MISO release, which is the one failure the enable exists to prevent. Chapter 14.5's argument is specific and the top level has to honour it.

"A top-level review is just the nine block reviews in one sitting." Three of the fourteen checklist items are relationships between parameters in different modules, and no single module can check them. Those findings exist only at this level.

12. Reason It Through

Q. The slave reports min_half = 2 with ratio_err clear. A reviewer says one of the two must be wrong. What is the answer?

Both are right, and they are answering different questions. min_half is the shortest interval the front end ever measured, including the closing edge of each transaction — which this master makes 2 cycles long at every divisor. ratio_err is gated on intervals that were followed by more clocking, which are the intervals a capture depended on; the closing interval is not one of those, because under CPHA=0 every bit was captured on a leading edge before it. The flag is a judgement about data and the number is an observation about the bus.

Q. Why would gating ratio_err on every interval have been worse than the exclusion, even though the exclusion loses coverage under CPHA=1?

Because it would fire on every CPHA=0 transaction that ever worked, and a flag that is always set is masked by the first integrator to see it — which loses the check entirely rather than losing one interval of it. The exclusion costs margin checking on one half-period per transaction; the alternative costs the whole flag.

Q. The phase detector's window is one cycle either side. What happens at HALF_MIN = 2, and what does that say about the parameter?

A matched master's bit settles HALF_MIN - 1 = 1 cycle before the sample, which is inside the window — so the detector would fire on correct configurations. HALF_MIN = 2 is legal for edge recovery but not sufficient for this detector, which means the detector adds a constraint the front end alone did not have. That is worth knowing explicitly: HALF_MIN >= 3 is a requirement of Chapter 14.6, not only of Chapter 14.1.

Q. Test 4 read the wrong byte in all four modes because the read index was stale. Why is that worth a paragraph in a chapter about a design?

Because the failure mode of the interface caused it. sys_rd_idx is a combinational window rather than an advancing pointer — a deliberate choice from Chapter 14.9, made so that the seqlock's retry can re-read the same words — and the cost of that choice is that every reader must set the index. A bench forgot, and a driver will too. The design decision and the bug it invites belong in the same place.

Q. A third device is added to the bus: another instance of this slave. Does the master's gap need to change?

No. Two identical oversampling slaves never contend at any gap, because the one being selected is exactly as slow to start driving as the one being deselected is to stop — the subtraction in Chapter 14.5's requirement is zero. The gap requirement appears only when the devices differ, so adding a copy is free and adding a different device is not. That asymmetry is why the requirement is documented as a formula rather than a number.

13. Understanding Check

14. Summary

Nine blocks, each verified against pin-level requirements rather than against the others, wired to the master of Chapter 13.11 byte for byte. They interoperate on the first attempt — because Module 14 stated its requirements as three numbers and Module 13 made all three programmable.

The one ordering decision is that the safe-idle gate comes second, before the transaction detector, because the detector cannot un-start a transaction. The one wiring subtlety is that the output enable takes the ungated select, so a stranded slave still releases MISO on time.

Integration found two things neither module's own bench could.

The master truncates the last half-period of every transaction — a fixed 2 cycles at every divisor — because it ends on its final capture. That edge carries no data under CPHA=0, so Chapter 14.1 was given the distinction it lacked: min_half measures every interval and ratio_err judges only those followed by more clocking. A flag and a number that deliberately disagree, with the cost under CPHA=1 documented rather than hidden.

A real master's MOSI appears one cycle after the edge that launched it, because the output is registered. Chapter 14.6's phase detector had to become a window, one cycle either side — the widest it can be while a matched pair still produces exactly zero events.

That chapter has now been corrected twice: once for a wrong premise, found by arithmetic, and once for a wrong model of the environment, found by integration. Neither was a coding error and both passed their tests, which is the clearest statement in either module of what a unit bench cannot do.

The review checklist has fourteen items, and three of them are relationships between parameters in different modules — invisible to every block and visible only here.

For verification: the strongest test of a block is another block that was not built to match it; misconfigure one requirement at a time and check the diagnosis names the right one; assert that a flag stays clear while its own measurement is out of range, so the decision cannot be quietly undone; and always check the transaction after the interesting one.

For implementation: SCLK is data, so no create_clock and three set_input_delays; the synchroniser chains need vendor attributes because their depth is load-bearing; the tri-state must be at the top level driving a port; and the whole design is one clock domain, which is what Chapter 14.1's choice bought.

15. What Comes Next

Module 14 built a slave on one architectural assumption — that the system clock is faster than SCLK — and every requirement in it came from that assumption's SYNC_N.

Module 15 removes the assumption. It builds the other architecture, the one clocked on SCLK, and compares the two with numbers rather than preferences: what the domain crossing at the system boundary costs, what the ratio requirement was worth, and which of the two a given system should choose. It also writes the constraints file that Module 14 kept deferring — the three input delays, the synchroniser attributes, and the create_clock that must not be there.

Continue learning