Skip to content
VLSI Mentor

SPI · Module 18

Bit Shifts, Bit-Order Mismatch, and Clock-Count Errors

A slip has a side, and only the wire factors it. But whether it is decidable at all is a property of the debug pattern — 0xFF lets a reversed link report a clean byte, and 0x01, the walking one, reports a rotation as a reversal.

Chapter 18.2 exonerated or named the mode. What is left is the family of faults that move data without breaking the frame, and which everybody describes with the same four words: the bits are shifted.

The debug pattern is part of the instrument. Choose it by habit and you have chosen your instrument by habit.

1. Three Faults That All "Shift The Bits"

FaultMechanism
Bit-order mismatchone end is MSB-first, the other LSB-first
Rotationan off-by-one in a shift register's load or capture
Clock-count errorthe master issued N+1 or N−1 pulses for an N-bit payload

Every one of them produces a structurally perfect capture — Chapter 18.1's triage returns EV_WELL for all three — carrying a permuted payload. And the phrase the bits are shifted is where the investigation usually stops, because it names a symptom that all three share.

2. A Slip Has A Side

This is the first of the chapter's two ideas, and it is the one a capture settles immediately.

The relation between what you intended and what was reported is the composition of two relations: what the master put on the wire, and what the slave did with it. A byte-level mismatch report gives you the composition. A pin capture factors it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   word_exp  ──rel_tx──▶  the bits on MOSI  ──rel_rx──▶  word_rx
             (the master)                    (the slave)

So the decoder below computes the relation twice. A master transmitting LSB-first and a slave receiving LSB-first produce the identical reported byte — the same mismatch, the same error counter, the same log line, and opposite ends of the link. The measurement in section 7 drives both and requires the two reported bytes to be equal before splitting them, so the discrimination is a result rather than a restatement of the stimulus.

3. Six Candidate Transforms

The decoder considers exactly six relations, and the choice is deliberate.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   X_IDENT   nothing happened
   X_REV     bit-order mismatch
   X_ROTL    a one-bit rotation left
   X_ROTR    a one-bit rotation right
   X_SHL     ONE EXTRA clock pulse   -- shifted up, MOSI's idle level shifted in
   X_SHR     ONE MISSING clock pulse -- shifted down, top bit never loaded

Six, and not more: widening the set to arbitrary rotations would make this decoder agree with any capture at all, which is the failure mode section 8 is about. The set is small enough that its collisions can be enumerated, and enumerating them is the chapter.

X_ROTL and X_SHL differ in exactly one bit — the one shifted in. So they are distinguishable only when that bit is observable, and that is a property of the payload rather than of the fault.

4. A Clock-Count Error Is A Shift, Not A Rotation

The ninth pulse shifts in MOSI's idle level

14 cycles
Five rows over fourteen cycles. Chip select is asserted until cycle twelve. SCLK produces leading edges at cycles two, six and ten — one more than the eight-bit payload requires. MOSI carries bit six then bit seven then rests low. A pulse-number row labels the two payload pulses and the extra one. A receiver shift-register row shows 0x8d after the eighth pulse and 0x1a after the ninth.leading edge: bit 6 sampledleading edge: bit 6 sampledbit 7 sampled — 8 bits completebit 7 sampled — 8 bitscompletethe NINTH pulse shifts in an idle 0the NINTH pulse shifts inan idle 0release: receiver holds 0x1arelease: receiver holds0x1acs_nsclkmosib6b6b6b6b7b7b7b70000XXpulsebit 6bit 6bit 6bit 6bit 7bit 7bit 7bit 7EXTRAEXTRAEXTRAEXTRArx shreg00000008d8d8d8d1a1a1at0t1t2t3t4t5t6t7t8t9t10t11t12t13
Figure 1 — the last two bits of an 8-bit frame, plus one extra clock pulse. MOSI rests at its idle level after bit 7, so the ninth pulse shifts a 0 into the receiver: it holds 0x1a, which is 0x8d shifted up with a 0 in the bottom — not a rotation. The wire over the payload's eight bit-times is clean, which is why the fault is the master's and presents as a slave-side transform. Horizontal scrolling is expected on a narrow screen; the figure is a timeline.

Three things follow from that figure.

The wire is clean. Over the payload's eight bit-times MOSI carried exactly the intended bits, so rel_tx is IDENT — and the fault is entirely the master's. A decoder that reported only where the data changed would blame the slave.

The receiver holds 0x1a, not a rotation of 0x8d. A rotation would have brought bit 7 around into the bottom; the extra pulse brought in MOSI's idle level instead. For 0x8d, whose top bit is 1, those differ and the fault is nameable. For a payload whose top bit is 0 they are the same value and it is not.

And the fault is doubly visible: eighteen SCLK edges is not 2N, so Chapter 18.1's triage flags EV_COUNT on the same capture. Two independent detectors agreeing on one fault is a feature, not redundancy — it is how you find out that one of them is broken.

5. Grading The Pattern

Here is the second idea, and it is the reason this chapter exists as more than a taxonomy.

Whether any of the six transforms is identifiable depends on the payload. Three one-bit properties predict most of it, and each one is derivable with a pencil:

PropertyConsequence
the payload is a palindrome over len bitsreversal maps it to itself → X_REV is invisible
its top bit is 0X_ROTL and X_SHL produce the same value
its bottom bit is 0X_ROTR and X_SHR produce the same value

So the module publishes those three bits alongside a grade: the number of distinct images the six transforms produce for this payload, from 6 (every candidate fault distinguishable) down to 1 (a capture says nothing at all). The grade is combinational in the payload — no clock, no frame, no capture — so a regression can ask whether its debug pattern is fit for purpose before it drives a single edge.

6. The Measurement — Grading Seven Patterns

No stimulus, no frames. Identical output from all three languages:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  pattern  grade  pal  msb0  lsb0  bound  verdict
     0x00      1    1     1     1      3  BLIND -- all six transforms give the same image
     0xff      3    1     0     0      5  weak -- at least three faults share a symptom
     0xaa      3    0     0     1      5  weak -- at least three faults share a symptom
     0x55      3    0     1     0      5  weak -- at least three faults share a symptom
     0x01      4    0     1     0      5  usable -- one pair still collides
     0x0f      5    0     1     0      5  usable -- one pair still collides
     0x8d      6    0     0     0      6  full -- every candidate fault is distinguishable

Read the first five rows and then reflect on which patterns a bring-up script actually uses. 0x00, 0xFF, 0xAA, 0x55 and 0x01 are the five most common debug payloads in the industry, and not one of them can separate the six faults. 0x00 cannot separate any of them: all six images are 0x00, so every one of these faults reports the expected byte.

Two rows are exact — 0x0f and 0x8d — and the other five lose more than the pencil rules predict.

7. The Measurement — Seven Frames, Two Relations Each

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  pat  wire  rx   rel_tx  amb  rel_rx  amb  expected_tx/rx     stimulus
  0x8d  0x8d  0x8d  IDENT     1  IDENT     1  IDENT /IDENT   a correct link, pattern 0x8d (grade 6)
  0x8d  0xb1  0xb1  REV       1  IDENT     1  REV   /IDENT   the MASTER transmits LSB-first
  0x8d  0x8d  0xb1  IDENT     1  REV       1  IDENT /REV     the SLAVE receives LSB-first -- same byte as above
  0x8d  0x8d  0x1a  IDENT     1  SHL       1  IDENT /SHL     ONE EXTRA clock pulse: clean wire, shifted receiver
  0xaa  0x55  0x55  REV       4  IDENT     1  REV   /IDENT   0xaa reversed: right answer, four hypotheses
  0xff  0xff  0xff  IDENT     4  IDENT     4  IDENT /IDENT   0xff reversed: a BROKEN link reports a clean byte
  0x01  0x80  0x80  REV       2  IDENT     1  REV   /IDENT   0x01 rotated right: reported REVERSED -- wrong

Rows 2 and 3 are the factorisation. Both report 0xb1. The rx column is identical; the wire column is not, and that single difference assigns the fault to a device.

Row 4 is the clock-count error from section 4: clean wire, rel_rx = SHL, ambiguity 1 — a unique answer, because 0x8d's top bit is 1.

Row 5 is the right answer for the wrong reason. rel_tx reads REV, which is correct, and amb reads 4: reversal, both rotations and a right shift all carry 0xaa to 0x55. The verdict is one hypothesis out of four and the module says so.

8. Two Results That Should Change How You Pick A Pattern

The practical rule is one line long, and it is the whole chapter: before trusting a slip diagnosis, grade the payload. If the grade is below 6, the capture cannot separate the candidates, and the fix is to change the stimulus rather than to stare harder at the waveform.

9. Building It — Three HDLs

Everything above is synthesizable. Bit reversal is wiring; the rotations and shifts are multiplexers; the relation set is six comparators; the grade is fifteen more.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_slip_diag.sv — the slip decoder — two relations, one per side of the pin, and a grade for the stimulus itself
// spi_slip_diag.sv
//
// Chapter 18.3 -- alignment faults, and the discovery that the DEBUG PATTERN is part of the
// instrument.
//
// THE FAULTS THIS CHAPTER IS ABOUT all move data without breaking the frame. Bit-order mismatch, a
// one-bit rotation, an extra or missing clock pulse: every one of them produces a well-formed
// capture carrying a permuted payload, and every one of them is routinely described as "the bits are
// shifted".
//
// TWO IDEAS, AND THE SECOND ONE IS THE CHAPTER.
//
// FIRST: A SLIP HAS A SIDE. The relation between what was intended and what was reported is the
// composition of two relations -- what the MASTER put on the wire, and what the SLAVE did with it --
// and only a pin capture can factor it. This module therefore computes the relation TWICE:
//
//     rel_tx    word_exp  ->  the bits actually captured on MOSI      (the master's doing)
//     rel_rx    the captured bits  ->  word_rx                        (the slave's doing)
//
// A master transmitting LSB-first and a slave receiving LSB-first produce the SAME reported byte. No
// amount of care with that byte separates them. The wire does, immediately, and that is the whole
// reason a capture is worth taking.
//
// SECOND, AND THIS IS THE POINT OF THE CHAPTER: WHETHER EITHER RELATION IS DECIDABLE AT ALL IS A
// PROPERTY OF THE PATTERN, NOT OF THE INSTRUMENT.
//
// Six candidate transforms are considered. For some payloads they produce six distinct images and
// any one of them can be identified. For others they collide, and a capture cannot tell which
// transform it is looking at. For 0x00 all six images are identical and the capture says NOTHING.
//
//     0xFF  reversal, rotate-left and rotate-right all map it to itself -- a broken link
//           reports the expected byte and the session ends with "works fine"
//     0xAA  reversal, rotate-left and rotate-right all produce 0x55 -- three faults, one symptom
//     0x01  the classic walking-one pattern: reversal and rotate-right BOTH give 0x80
//
// So the module grades the pattern as well as the capture, combinationally and without needing a
// frame, and it publishes three one-bit properties of the payload that PREDICT the collisions:
//
//     pat_pal    the payload is a palindrome over `len` bits   ->  reversal is invisible
//     pat_msb0   its top bit is 0                              ->  rotate-left == shift-left
//     pat_lsb0   its bottom bit is 0                           ->  rotate-right == shift-right
//
// Those three rules are derivable with a pencil, and the bench checks the derivation against the
// measured grade for seven payloads. A prediction and a measurement that must agree is a far better
// bug detector than either alone -- which is the same argument Chapter 17.4 made about reporting the
// work a solver did alongside its result.

`timescale 1ns/1ps

module spi_slip_diag #(
    parameter int DW    = 32,
    parameter int LEN_W = 6
) (
    input  wire              clk,
    input  wire              rst_n,

    // The pins.
    input  wire              sclk,
    input  wire              cs_n,
    input  wire              mosi,

    input  wire              cpol,
    input  wire              cpha,
    input  wire [LEN_W-1:0]  len,

    // What the transfer should have carried, and what the receiver reported.
    input  wire [DW-1:0]     word_exp,
    input  wire [DW-1:0]     word_rx,

    // STIMULUS GRADING. Combinational in `word_exp` and `len`: no frame, no clock, no capture. A
    // bench can ask "is this pattern able to tell my candidate faults apart?" before it drives
    // anything, which is the order the question ought to be asked in.
    output wire [3:0]        pat_grade,   // distinct images under the six transforms, 1..6
    output wire              pat_pal,
    output wire              pat_msb0,
    output wire              pat_lsb0,

    // PER-FRAME DIAGNOSIS.
    output reg               dg_valid,
    output reg  [DW-1:0]     ob_word,     // the bits actually captured on MOSI
    output reg  [2:0]        rel_tx,      // word_exp -> wire
    output reg  [2:0]        rel_rx,      // wire     -> word_rx
    output reg  [3:0]        amb_tx,      // how many transforms explain rel_tx (1 = unique)
    output reg  [3:0]        amb_rx
);

    // The six candidate transforms, and why exactly these six.
    //
    //   X_IDENT  nothing happened
    //   X_REV    bit-order mismatch -- one end is MSB-first and the other LSB-first
    //   X_ROTL   a one-bit rotation left: the classic off-by-one in a shift register's load
    //   X_ROTR   a one-bit rotation right
    //   X_SHL    ONE EXTRA clock pulse. The receiver shifted nine times for eight bits, so it holds
    //            the payload shifted up with whatever MOSI was resting at shifted in.
    //   X_SHR    ONE MISSING clock pulse: the payload shifted down, top bit never loaded.
    //
    // X_ROTL AND X_SHL DIFFER IN EXACTLY ONE BIT -- the one shifted in -- so a payload whose top bit
    // is 0 makes a rotation and a clock-count error IDENTICAL. That is a different mechanism from
    // Chapter 18.2's blind spot (there the observation did not exist; here two observations exist and
    // coincide) and it is why `pat_msb0` is published as a warning rather than left implicit.
    localparam [2:0] X_IDENT = 3'd0,
                     X_REV   = 3'd1,
                     X_ROTL  = 3'd2,
                     X_ROTR  = 3'd3,
                     X_SHL   = 3'd4,
                     X_SHR   = 3'd5,
                     X_NONE  = 3'd6;   // no candidate transform explains the pair

    // One transform, applied to the low `nb` bits and zero above them.
    //
    // Every index here is a variable index into a fixed-width vector rather than a part-select,
    // because `len` is a run-time input: `w[nb-2:0]` is not legal Verilog when `nb` is not a
    // constant, and writing it that way is a mistake that compiles in some tools by silently
    // freezing the width.
    function [DW-1:0] xf(input integer k, input [DW-1:0] w, input integer nb);
        integer i;
        reg [DW-1:0] r;
        begin
            r = {DW{1'b0}};
            for (i = 0; i < DW; i = i + 1) begin
                if (i < nb) begin
                    case (k)
                        0: r[i] = w[i];
                        1: r[i] = w[nb-1-i];
                        2: r[i] = (i == 0)      ? w[nb-1] : w[i-1];
                        3: r[i] = (i == nb-1)   ? w[0]    : w[i+1];
                        4: r[i] = (i == 0)      ? 1'b0    : w[i-1];
                        5: r[i] = (i == nb-1)   ? 1'b0    : w[i+1];
                        default: r[i] = w[i];
                    endcase
                end
            end
            xf = r;
        end
    endfunction

    // Which transforms carry `a` to `b`. A SET, not a winner -- because the size of the set is the
    // measurement that says whether a winner means anything.
    function [5:0] relmask(input [DW-1:0] a, input [DW-1:0] b, input integer nb);
        integer k;
        reg [DW-1:0] m;
        begin
            m = {DW{1'b0}};
            for (k = 0; k < DW; k = k + 1) if (k < nb) m[k] = 1'b1;
            relmask = 6'b0;
            for (k = 0; k < 6; k = k + 1)
                relmask[k] = ((xf(k, a, nb) & m) == (b & m));
        end
    endfunction

    function [3:0] popc6(input [5:0] m);
        integer i;
        begin
            popc6 = 4'd0;
            for (i = 0; i < 6; i = i + 1) popc6 = popc6 + {3'b000, m[i]};
        end
    endfunction

    // THE PUBLISHED PRIORITY. When several transforms explain the same pair the module still reports
    // one, and it reports the simplest -- identity before a permutation, a permutation before a
    // clock-count error -- because a reader who ignores `amb` should at least be pointed at the
    // hypothesis that requires the fewest assumptions. `amb` is the number that says whether to
    // believe it, and Chapter 18.1's argument applies unchanged: a priority that is not published
    // produces arguments about classification.
    function [2:0] rel_of(input [5:0] m);
        begin
            if      (m[0]) rel_of = X_IDENT;
            else if (m[1]) rel_of = X_REV;
            else if (m[2]) rel_of = X_ROTL;
            else if (m[3]) rel_of = X_ROTR;
            else if (m[4]) rel_of = X_SHL;
            else if (m[5]) rel_of = X_SHR;
            else           rel_of = X_NONE;
        end
    endfunction

    // How many DISTINCT images the six transforms produce for this payload. This is the grade, and it
    // is a property of the stimulus alone.
    function [3:0] gradef(input [DW-1:0] w, input integer nb);
        integer j, k;
        reg dup;
        begin
            gradef = 4'd0;
            for (j = 0; j < 6; j = j + 1) begin
                dup = 1'b0;
                for (k = 0; k < 6; k = k + 1)
                    if (k < j && xf(k, w, nb) == xf(j, w, nb)) dup = 1'b1;
                if (!dup) gradef = gradef + 4'd1;
            end
        end
    endfunction

    wire [31:0] nb = {{(32-LEN_W){1'b0}}, len};

    assign pat_grade = gradef(word_exp, nb);
    assign pat_pal   = (xf(1, word_exp, nb) == xf(0, word_exp, nb));
    assign pat_msb0  = ~word_exp[nb-1];
    assign pat_lsb0  = ~word_exp[0];

    // ---- the capture ----
    //
    // The same mechanism Chapters 18.1 and 18.2 built and justified, reduced to its essentials
    // because it has been explained twice: `leading` is an SCLK change away from the idle level,
    // `capture` is the leading edge for CPHA=0 and the trailing edge for CPHA=1, and a release is
    // part of the transaction that is ending.
    //
    // ONE DETAIL IS SPECIFIC TO THIS CHAPTER. The capture is bounded by `nb`, so a frame carrying an
    // EXTRA clock pulse is captured as its first `nb` bits -- the intended payload -- while the
    // receiver's own shift register holds the last `nb` of nb+1. That asymmetry is exactly what makes
    // a clock-count error appear as a slave-side transform with a clean wire, and it is the reason
    // this module reports `rel_rx = X_SHL` for a fault that is entirely the master's.
    reg              sclk_d, cs_n_d;
    reg  [DW-1:0]    acc;
    reg  [LEN_W:0]   nseen;

    wire cs_assert   =  cs_n_d & ~cs_n;
    wire cs_deassert = ~cs_n_d &  cs_n;
    wire in_txn      = ~cs_n | cs_deassert;
    wire sclk_edge   = (sclk !== sclk_d);
    wire leading     = sclk_edge && (sclk !== cpol);
    wire capture     = (cpha ? (sclk_edge && !leading) : leading) && in_txn;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sclk_d   <= 1'b0;
            cs_n_d   <= 1'b1;
            acc      <= {DW{1'b0}};
            nseen    <= {(LEN_W+1){1'b0}};
            dg_valid <= 1'b0;
            ob_word  <= {DW{1'b0}};
            rel_tx   <= X_IDENT;
            rel_rx   <= X_IDENT;
            amb_tx   <= 4'd0;
            amb_rx   <= 4'd0;
        end else begin
            dg_valid <= 1'b0;

            if (cs_assert) begin
                acc   <= {DW{1'b0}};
                nseen <= {(LEN_W+1){1'b0}};
            end else begin
                if (capture && (nseen < nb)) begin
                    acc[nb - 1 - nseen] <= mosi;
                    nseen               <= nseen + 1'b1;
                end
            end

            if (cs_deassert) begin
                dg_valid <= 1'b1;
                ob_word  <= acc;
                // TWO RELATIONS, ONE PER SIDE OF THE PIN. This is the factorisation, and it is the
                // only thing here that a byte-level log cannot reproduce.
                rel_tx   <= rel_of(relmask(word_exp, acc,     nb));
                rel_rx   <= rel_of(relmask(acc,      word_rx, nb));
                amb_tx   <= popc6(relmask(word_exp, acc,     nb));
                amb_rx   <= popc6(relmask(acc,      word_rx, nb));
            end

            sclk_d <= sclk;
            cs_n_d <= cs_n;
        end
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slip_diag.v — the same design in Verilog-2001
// spi_slip_diag.v
//
// Chapter 18.3 -- alignment faults, and the discovery that the DEBUG PATTERN is part of the
// instrument.
//
// THE FAULTS THIS CHAPTER IS ABOUT all move data without breaking the frame. Bit-order mismatch, a
// one-bit rotation, an extra or missing clock pulse: every one of them produces a well-formed
// capture carrying a permuted payload, and every one of them is routinely described as "the bits are
// shifted".
//
// TWO IDEAS, AND THE SECOND ONE IS THE CHAPTER.
//
// FIRST: A SLIP HAS A SIDE. The relation between what was intended and what was reported is the
// composition of two relations -- what the MASTER put on the wire, and what the SLAVE did with it --
// and only a pin capture can factor it. This module therefore computes the relation TWICE:
//
//     rel_tx    word_exp  ->  the bits actually captured on MOSI      (the master's doing)
//     rel_rx    the captured bits  ->  word_rx                        (the slave's doing)
//
// A master transmitting LSB-first and a slave receiving LSB-first produce the SAME reported byte. No
// amount of care with that byte separates them. The wire does, immediately, and that is the whole
// reason a capture is worth taking.
//
// SECOND, AND THIS IS THE POINT OF THE CHAPTER: WHETHER EITHER RELATION IS DECIDABLE AT ALL IS A
// PROPERTY OF THE PATTERN, NOT OF THE INSTRUMENT.
//
// Six candidate transforms are considered. For some payloads they produce six distinct images and
// any one of them can be identified. For others they collide, and a capture cannot tell which
// transform it is looking at. For 0x00 all six images are identical and the capture says NOTHING.
//
//     0xFF  reversal, rotate-left and rotate-right all map it to itself -- a broken link
//           reports the expected byte and the session ends with "works fine"
//     0xAA  reversal, rotate-left and rotate-right all produce 0x55 -- three faults, one symptom
//     0x01  the classic walking-one pattern: reversal and rotate-right BOTH give 0x80
//
// So the module grades the pattern as well as the capture, combinationally and without needing a
// frame, and it publishes three one-bit properties of the payload that PREDICT the collisions:
//
//     pat_pal    the payload is a palindrome over `len` bits   ->  reversal is invisible
//     pat_msb0   its top bit is 0                              ->  rotate-left == shift-left
//     pat_lsb0   its bottom bit is 0                           ->  rotate-right == shift-right
//
// Those three rules are derivable with a pencil, and the bench checks the derivation against the
// measured grade for seven payloads. A prediction and a measurement that must agree is a far better
// bug detector than either alone -- which is the same argument Chapter 17.4 made about reporting the
// work a solver did alongside its result.

`timescale 1ns/1ps

module spi_slip_diag #(
    parameter DW    = 32,
    parameter LEN_W = 6
) (
    input  wire              clk,
    input  wire              rst_n,

    // The pins.
    input  wire              sclk,
    input  wire              cs_n,
    input  wire              mosi,

    input  wire              cpol,
    input  wire              cpha,
    input  wire [LEN_W-1:0]  len,

    // What the transfer should have carried, and what the receiver reported.
    input  wire [DW-1:0]     word_exp,
    input  wire [DW-1:0]     word_rx,

    // STIMULUS GRADING. Combinational in `word_exp` and `len`: no frame, no clock, no capture. A
    // bench can ask "is this pattern able to tell my candidate faults apart?" before it drives
    // anything, which is the order the question ought to be asked in.
    output wire [3:0]        pat_grade,   // distinct images under the six transforms, 1..6
    output wire              pat_pal,
    output wire              pat_msb0,
    output wire              pat_lsb0,

    // PER-FRAME DIAGNOSIS.
    output reg               dg_valid,
    output reg  [DW-1:0]     ob_word,     // the bits actually captured on MOSI
    output reg  [2:0]        rel_tx,      // word_exp -> wire
    output reg  [2:0]        rel_rx,      // wire     -> word_rx
    output reg  [3:0]        amb_tx,      // how many transforms explain rel_tx (1 = unique)
    output reg  [3:0]        amb_rx
);

    // The six candidate transforms, and why exactly these six.
    //
    //   X_IDENT  nothing happened
    //   X_REV    bit-order mismatch -- one end is MSB-first and the other LSB-first
    //   X_ROTL   a one-bit rotation left: the classic off-by-one in a shift register's load
    //   X_ROTR   a one-bit rotation right
    //   X_SHL    ONE EXTRA clock pulse. The receiver shifted nine times for eight bits, so it holds
    //            the payload shifted up with whatever MOSI was resting at shifted in.
    //   X_SHR    ONE MISSING clock pulse: the payload shifted down, top bit never loaded.
    //
    // X_ROTL AND X_SHL DIFFER IN EXACTLY ONE BIT -- the one shifted in -- so a payload whose top bit
    // is 0 makes a rotation and a clock-count error IDENTICAL. That is a different mechanism from
    // Chapter 18.2's blind spot (there the observation did not exist; here two observations exist and
    // coincide) and it is why `pat_msb0` is published as a warning rather than left implicit.
    localparam [2:0] X_IDENT = 3'd0,
                     X_REV   = 3'd1,
                     X_ROTL  = 3'd2,
                     X_ROTR  = 3'd3,
                     X_SHL   = 3'd4,
                     X_SHR   = 3'd5,
                     X_NONE  = 3'd6;   // no candidate transform explains the pair

    // One transform, applied to the low `nb` bits and zero above them.
    //
    // Every index here is a variable index into a fixed-width vector rather than a part-select,
    // because `len` is a run-time input: `w[nb-2:0]` is not legal Verilog when `nb` is not a
    // constant, and writing it that way is a mistake that compiles in some tools by silently
    // freezing the width.
        function [DW-1:0] xf;
        input integer k;
        input [DW-1:0] w;
        input integer nb;
        integer i;
        reg [DW-1:0] r;
        begin
            r = {DW{1'b0}};
            for (i = 0; i < DW; i = i + 1) begin
                if (i < nb) begin
                    case (k)
                        0: r[i] = w[i];
                        1: r[i] = w[nb-1-i];
                        2: r[i] = (i == 0)      ? w[nb-1] : w[i-1];
                        3: r[i] = (i == nb-1)   ? w[0]    : w[i+1];
                        4: r[i] = (i == 0)      ? 1'b0    : w[i-1];
                        5: r[i] = (i == nb-1)   ? 1'b0    : w[i+1];
                        default: r[i] = w[i];
                    endcase
                end
            end
            xf = r;
        end
    endfunction

    // Which transforms carry `a` to `b`. A SET, not a winner -- because the size of the set is the
    // measurement that says whether a winner means anything.
        function [5:0] relmask;
        input [DW-1:0] a;
        input [DW-1:0] b;
        input integer nb;
        integer k;
        reg [DW-1:0] m;
        begin
            m = {DW{1'b0}};
            for (k = 0; k < DW; k = k + 1) if (k < nb) m[k] = 1'b1;
            relmask = 6'b0;
            for (k = 0; k < 6; k = k + 1)
                relmask[k] = ((xf(k, a, nb) & m) == (b & m));
        end
    endfunction

        function [3:0] popc6;
        input [5:0] m;
        integer i;
        begin
            popc6 = 4'd0;
            for (i = 0; i < 6; i = i + 1) popc6 = popc6 + {3'b000, m[i]};
        end
    endfunction

    // THE PUBLISHED PRIORITY. When several transforms explain the same pair the module still reports
    // one, and it reports the simplest -- identity before a permutation, a permutation before a
    // clock-count error -- because a reader who ignores `amb` should at least be pointed at the
    // hypothesis that requires the fewest assumptions. `amb` is the number that says whether to
    // believe it, and Chapter 18.1's argument applies unchanged: a priority that is not published
    // produces arguments about classification.
        function [2:0] rel_of;
        input [5:0] m;
        begin
            if      (m[0]) rel_of = X_IDENT;
            else if (m[1]) rel_of = X_REV;
            else if (m[2]) rel_of = X_ROTL;
            else if (m[3]) rel_of = X_ROTR;
            else if (m[4]) rel_of = X_SHL;
            else if (m[5]) rel_of = X_SHR;
            else           rel_of = X_NONE;
        end
    endfunction

    // How many DISTINCT images the six transforms produce for this payload. This is the grade, and it
    // is a property of the stimulus alone.
        function [3:0] gradef;
        input [DW-1:0] w;
        input integer nb;
        integer j, k;
        reg dup;
        begin
            gradef = 4'd0;
            for (j = 0; j < 6; j = j + 1) begin
                dup = 1'b0;
                for (k = 0; k < 6; k = k + 1)
                    if (k < j && xf(k, w, nb) == xf(j, w, nb)) dup = 1'b1;
                if (!dup) gradef = gradef + 4'd1;
            end
        end
    endfunction

    wire [31:0] nb = {{(32-LEN_W){1'b0}}, len};

    assign pat_grade = gradef(word_exp, nb);
    assign pat_pal   = (xf(1, word_exp, nb) == xf(0, word_exp, nb));
    assign pat_msb0  = ~word_exp[nb-1];
    assign pat_lsb0  = ~word_exp[0];

    // ---- the capture ----
    //
    // The same mechanism Chapters 18.1 and 18.2 built and justified, reduced to its essentials
    // because it has been explained twice: `leading` is an SCLK change away from the idle level,
    // `capture` is the leading edge for CPHA=0 and the trailing edge for CPHA=1, and a release is
    // part of the transaction that is ending.
    //
    // ONE DETAIL IS SPECIFIC TO THIS CHAPTER. The capture is bounded by `nb`, so a frame carrying an
    // EXTRA clock pulse is captured as its first `nb` bits -- the intended payload -- while the
    // receiver's own shift register holds the last `nb` of nb+1. That asymmetry is exactly what makes
    // a clock-count error appear as a slave-side transform with a clean wire, and it is the reason
    // this module reports `rel_rx = X_SHL` for a fault that is entirely the master's.
    reg              sclk_d, cs_n_d;
    reg  [DW-1:0]    acc;
    reg  [LEN_W:0]   nseen;

    wire cs_assert   =  cs_n_d & ~cs_n;
    wire cs_deassert = ~cs_n_d &  cs_n;
    wire in_txn      = ~cs_n | cs_deassert;
    wire sclk_edge   = (sclk !== sclk_d);
    wire leading     = sclk_edge && (sclk !== cpol);
    wire capture     = (cpha ? (sclk_edge && !leading) : leading) && in_txn;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sclk_d   <= 1'b0;
            cs_n_d   <= 1'b1;
            acc      <= {DW{1'b0}};
            nseen    <= {(LEN_W+1){1'b0}};
            dg_valid <= 1'b0;
            ob_word  <= {DW{1'b0}};
            rel_tx   <= X_IDENT;
            rel_rx   <= X_IDENT;
            amb_tx   <= 4'd0;
            amb_rx   <= 4'd0;
        end else begin
            dg_valid <= 1'b0;

            if (cs_assert) begin
                acc   <= {DW{1'b0}};
                nseen <= {(LEN_W+1){1'b0}};
            end else begin
                if (capture && (nseen < nb)) begin
                    acc[nb - 1 - nseen] <= mosi;
                    nseen               <= nseen + 1'b1;
                end
            end

            if (cs_deassert) begin
                dg_valid <= 1'b1;
                ob_word  <= acc;
                // TWO RELATIONS, ONE PER SIDE OF THE PIN. This is the factorisation, and it is the
                // only thing here that a byte-level log cannot reproduce.
                rel_tx   <= rel_of(relmask(word_exp, acc,     nb));
                rel_rx   <= rel_of(relmask(acc,      word_rx, nb));
                amb_tx   <= popc6(relmask(word_exp, acc,     nb));
                amb_rx   <= popc6(relmask(acc,      word_rx, nb));
            end

            sclk_d <= sclk;
            cs_n_d <= cs_n;
        end
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slip_diag.vhd — the same design in VHDL
-- spi_slip_diag.vhd
--
-- Chapter 18.3 -- alignment faults, and the discovery that the DEBUG PATTERN is part of the
-- instrument.
--
-- THE FAULTS THIS CHAPTER IS ABOUT all move data without breaking the frame. Bit-order mismatch, a
-- one-bit rotation, an extra or missing clock pulse: every one of them produces a well-formed
-- capture carrying a permuted payload, and every one of them is routinely described as "the bits are
-- shifted".
--
-- TWO IDEAS, AND THE SECOND ONE IS THE CHAPTER.
--
-- FIRST: A SLIP HAS A SIDE. The relation between what was intended and what was reported is the
-- composition of two relations -- what the MASTER put on the wire, and what the SLAVE did with it --
-- and only a pin capture can factor it. This module therefore computes the relation TWICE:
--
--     rel_tx    word_exp  ->  the bits actually captured on MOSI      (the master's doing)
--     rel_rx    the captured bits  ->  word_rx                        (the slave's doing)
--
-- A master transmitting LSB-first and a slave receiving LSB-first produce the SAME reported byte. No
-- amount of care with that byte separates them. The wire does, immediately, and that is the whole
-- reason a capture is worth taking.
--
-- SECOND, AND THIS IS THE POINT OF THE CHAPTER: WHETHER EITHER RELATION IS DECIDABLE AT ALL IS A
-- PROPERTY OF THE PATTERN, NOT OF THE INSTRUMENT.
--
-- Six candidate transforms are considered. For some payloads they produce six distinct images and
-- any one of them can be identified. For others they collide, and a capture cannot tell which
-- transform it is looking at. For 0x00 all six images are identical and the capture says NOTHING.
--
--     0xFF  reversal, rotate-left and rotate-right all map it to itself -- a broken link
--           reports the expected byte and the session ends with "works fine"
--     0xAA  reversal, rotate-left and rotate-right all produce 0x55 -- three faults, one symptom
--     0x01  the classic walking-one pattern: reversal and rotate-right BOTH give 0x80
--
-- So the module grades the pattern as well as the capture, combinationally and without needing a
-- frame, and it publishes three one-bit properties of the payload that PREDICT the collisions:
--
--     pat_pal    the payload is a palindrome over `len` bits   ->  reversal is invisible
--     pat_msb0   its top bit is 0                              ->  rotate-left == shift-left
--     pat_lsb0   its bottom bit is 0                           ->  rotate-right == shift-right
--
-- Those three rules are derivable with a pencil, and the bench checks the derivation against the
-- measured grade for seven payloads. A prediction and a measurement that must agree is a far better
-- bug detector than either alone -- which is the same argument Chapter 17.4 made about reporting the
-- work a solver did alongside its result.

--
-- WHAT THE VHDL VERSION ADDS. The six candidate transforms become an ENUMERATION and the transform
-- function takes that enumeration, so applying transform 7 is not expressible. In the Verilog
-- versions the transform selector is an `integer` and the `default` arm of its case statement exists
-- to catch a value that should not be constructible -- a defensive branch that cannot be reached and
-- therefore cannot be tested. Here the type does that work and the branch is gone.
--
-- The relation SET is a `boolean_vector`, which makes the distinction the chapter rests on visible at
-- the declaration: the module produces a set of candidate transforms and separately reports one of
-- them, and nothing about a set of size four resembles a diagnosis.
--
-- IDENTIFIER REVIEW (VHDL IS CASE-INSENSITIVE). Generics are `DW_C` and `LEN_W`; the length input is
-- `len` and the integer copy of it inside every subprogram is `nb`. No generic, port, signal,
-- variable, constant or subprogram argument here is distinguished from another only by case -- the
-- check that Chapter 17.4 learned to run after a variable `tries` silently became the generic
-- `TRIES` and a rejection loop stopped executing with no diagnostic anywhere.

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

package spi_slip_pkg is

    constant DW_C : natural := 32;

    -- The six candidates, plus the honest seventh: no candidate explains this pair.
    type slip_xf_t is (X_IDENT, X_REV, X_ROTL, X_ROTR, X_SHL, X_SHR, X_NONE);

    -- The relation SET. Its SIZE is the measurement; the single transform reported alongside it is a
    -- convenience for a reader who only wants the simplest hypothesis.
    type xf_set_t is array (0 to 5) of boolean;

    function apply_xf (k : slip_xf_t; w : std_logic_vector; nb : natural)
        return std_logic_vector;
    function rel_set  (a, b : std_logic_vector; nb : natural) return xf_set_t;
    function set_size (s : xf_set_t) return natural;
    function first_of (s : xf_set_t) return slip_xf_t;
    function grade_of (w : std_logic_vector; nb : natural) return natural;
    function xf_name  (k : slip_xf_t) return string;

end package spi_slip_pkg;

package body spi_slip_pkg is

    -- One transform over the low `nb` bits, zero above them.
    function apply_xf (k : slip_xf_t; w : std_logic_vector; nb : natural)
        return std_logic_vector is
        variable r : std_logic_vector(DW_C - 1 downto 0) := (others => '0');
    begin
        for i in 0 to DW_C - 1 loop
            if i < nb then
                case k is
                    when X_IDENT => r(i) := w(i);
                    when X_REV   => r(i) := w(nb - 1 - i);
                    when X_ROTL  => if i = 0      then r(i) := w(nb - 1); else r(i) := w(i - 1); end if;
                    when X_ROTR  => if i = nb - 1 then r(i) := w(0);      else r(i) := w(i + 1); end if;
                    when X_SHL   => if i = 0      then r(i) := '0';       else r(i) := w(i - 1); end if;
                    when X_SHR   => if i = nb - 1 then r(i) := '0';       else r(i) := w(i + 1); end if;
                    when others  => r(i) := w(i);
                end case;
            end if;
        end loop;
        return r;
    end function apply_xf;

    -- Which of the six carry `a` to `b`, compared only over the low `nb` bits.
    function rel_set (a, b : std_logic_vector; nb : natural) return xf_set_t is
        variable s : xf_set_t := (others => false);
        variable m : std_logic_vector(DW_C - 1 downto 0) := (others => '0');
    begin
        for i in 0 to DW_C - 1 loop
            if i < nb then m(i) := '1'; end if;
        end loop;
        for j in 0 to 5 loop
            s(j) := ((apply_xf(slip_xf_t'val(j), a, nb) and m) = (b and m));
        end loop;
        return s;
    end function rel_set;

    function set_size (s : xf_set_t) return natural is
        variable n : natural := 0;
    begin
        for j in 0 to 5 loop
            if s(j) then n := n + 1; end if;
        end loop;
        return n;
    end function set_size;

    -- The published priority: identity before a permutation, a permutation before a clock-count
    -- error, so a reader who ignores the set size is at least pointed at the hypothesis that needs
    -- the fewest assumptions.
    function first_of (s : xf_set_t) return slip_xf_t is
    begin
        for j in 0 to 5 loop
            if s(j) then return slip_xf_t'val(j); end if;
        end loop;
        return X_NONE;
    end function first_of;

    -- How many DISTINCT images the six transforms produce for this payload: a property of the
    -- stimulus alone, computable before any traffic exists.
    function grade_of (w : std_logic_vector; nb : natural) return natural is
        variable n   : natural := 0;
        variable dup : boolean;
    begin
        for j in 0 to 5 loop
            dup := false;
            for k in 0 to 5 loop
                if k < j and apply_xf(slip_xf_t'val(k), w, nb) = apply_xf(slip_xf_t'val(j), w, nb) then
                    dup := true;
                end if;
            end loop;
            if not dup then n := n + 1; end if;
        end loop;
        return n;
    end function grade_of;

    function xf_name (k : slip_xf_t) return string is
    begin
        case k is
            when X_IDENT => return "IDENT ";
            when X_REV   => return "REV   ";
            when X_ROTL  => return "ROTL  ";
            when X_ROTR  => return "ROTR  ";
            when X_SHL   => return "SHL   ";
            when X_SHR   => return "SHR   ";
            when others  => return "NONE  ";
        end case;
    end function xf_name;

end package body spi_slip_pkg;

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

entity spi_slip_diag is
    generic (
        LEN_W : positive := 6
    );
    port (
        clk      : in  std_logic;
        rst_n    : in  std_logic;

        sclk     : in  std_logic;
        cs_n     : in  std_logic;
        mosi     : in  std_logic;

        cpol     : in  std_logic;
        cpha     : in  std_logic;
        len      : in  unsigned(LEN_W - 1 downto 0);

        word_exp : in  std_logic_vector(DW_C - 1 downto 0);
        word_rx  : in  std_logic_vector(DW_C - 1 downto 0);

        -- Stimulus grading: combinational, no frame required.
        pat_grade : out natural;
        pat_pal   : out boolean;
        pat_msb0  : out boolean;
        pat_lsb0  : out boolean;

        dg_valid : out std_logic;
        ob_word  : out std_logic_vector(DW_C - 1 downto 0);
        rel_tx   : out slip_xf_t;
        rel_rx   : out slip_xf_t;
        amb_tx   : out natural;
        amb_rx   : out natural
    );
end entity spi_slip_diag;

architecture rtl of spi_slip_diag is
    signal nb   : natural := 0;
    signal v_r  : std_logic := '0';
    signal w_r  : std_logic_vector(DW_C - 1 downto 0) := (others => '0');
    signal tx_r : slip_xf_t := X_IDENT;
    signal rx_r : slip_xf_t := X_IDENT;
    signal atx_r, arx_r : natural := 0;
begin

    nb        <= to_integer(len);
    pat_grade <= grade_of(word_exp, to_integer(len));
    pat_pal   <= (apply_xf(X_REV, word_exp, to_integer(len))
                  = apply_xf(X_IDENT, word_exp, to_integer(len)));
    pat_msb0  <= (word_exp(to_integer(len) - 1) = '0');
    pat_lsb0  <= (word_exp(0) = '0');

    dg_valid <= v_r;
    ob_word  <= w_r;
    rel_tx   <= tx_r;
    rel_rx   <= rx_r;
    amb_tx   <= atx_r;
    amb_rx   <= arx_r;

    process (clk, rst_n) is
        variable sclk_d, cs_n_d       : std_logic;
        variable cs_assert, cs_deassert : boolean;
        variable in_txn, sclk_edge    : boolean;
        variable leading, capture     : boolean;
        variable acc                  : std_logic_vector(DW_C - 1 downto 0);
        variable nseen                : natural;
        variable n                    : natural;
        variable stx, srx             : xf_set_t;
    begin
        if rst_n = '0' then
            sclk_d := '0'; cs_n_d := '1';
            acc := (others => '0'); nseen := 0;
            v_r <= '0'; w_r <= (others => '0');
            tx_r <= X_IDENT; rx_r <= X_IDENT; atx_r <= 0; arx_r <= 0;

        elsif rising_edge(clk) then
            v_r <= '0';
            -- `nb` is read from the PORT rather than from the concurrent signal above, because a
            -- concurrent assignment is one delta stale inside a clocked process -- the defect that
            -- made two languages disagree in Chapter 17.2 and cost a full debugging session.
            n := to_integer(len);

            cs_assert   := (cs_n = '0') and (cs_n_d = '1');
            cs_deassert := (cs_n = '1') and (cs_n_d = '0');
            in_txn      := (cs_n = '0') or cs_deassert;
            sclk_edge   := (sclk /= sclk_d);
            leading     := sclk_edge and (sclk /= cpol);
            if cpha = '0' then capture := leading and in_txn;
            else               capture := sclk_edge and (not leading) and in_txn;
            end if;

            if cs_assert then
                acc := (others => '0'); nseen := 0;
            else
                -- Bounded by `nb`, so a frame carrying an EXTRA clock pulse is captured as its first
                -- nb bits -- the intended payload -- while the receiver's own register holds the last
                -- nb of nb+1. That asymmetry is what makes a master's clock-count error present as a
                -- slave-side transform over a clean wire.
                if capture and nseen < n then
                    acc(n - 1 - nseen) := mosi;
                    nseen              := nseen + 1;
                end if;
            end if;

            if cs_deassert then
                stx := rel_set(word_exp, acc,     n);
                srx := rel_set(acc,      word_rx, n);
                v_r   <= '1';
                w_r   <= acc;
                tx_r  <= first_of(stx);
                rx_r  <= first_of(srx);
                atx_r <= set_size(stx);
                arx_r <= set_size(srx);
            end if;

            sclk_d := sclk;
            cs_n_d := cs_n;
        end if;
    end process;

end architecture rtl;

The Bench

Azvya Education Pvt. Ltd.VLSI Mentor
spi_slip_diag_tb.sv — two tables: seven payloads graded with no stimulus, and seven frames factored into master and slave
// spi_slip_diag_tb.sv
//
// TWO TABLES, BECAUSE THERE ARE TWO MEASUREMENTS AND ONE OF THEM NEEDS NO STIMULUS AT ALL.
//
// TABLE A grades seven payloads. It drives no frames and needs no clock: the grade is combinational
// in the payload, which is the whole reason a bench can ask "can this pattern tell my candidate
// faults apart?" BEFORE it commits to a regression.
//
// TABLE B drives seven frames and asks, for each, what happened on each side of the pin.
//
// THE FOUR RESULTS.
//
//   1. A SLIP HAS A SIDE, AND ONLY THE WIRE REVEALS IT. A master transmitting LSB-first and a slave
//      receiving LSB-first report the IDENTICAL byte. The bench requires the two reported bytes to be
//      equal -- so the discrimination is a real result and not a restatement of the stimulus -- and
//      then splits them on `rel_tx` against `rel_rx`.
//
//   2. THE THREE PENCIL RULES ARE SOUND BUT NOT COMPLETE. A palindrome hides reversal, a top bit of 0
//      makes rotate-left and shift-left identical, a bottom bit of 0 does the same on the right. Those
//      three predict an UPPER BOUND on the grade. The measurement comes in at or below it for all
//      seven payloads and STRICTLY below for five of them, because a payload with period 2 is carried
//      to the same image by reversal and by either rotation -- a fourth mechanism the rules do not
//      express. A prediction that is checked against a measurement is worth more than either alone.
//
//   3. A BROKEN LINK REPORTS A CLEAN BYTE. With payload 0xFF a master that reverses the bit order
//      puts 0xFF on the wire and the receiver reports 0xFF. Every value in the system is correct. The
//      only thing that says otherwise is the AMBIGUITY COUNT -- four transforms explain the pair, so
//      "nothing happened" is one of four hypotheses rather than a finding.
//
//   4. THE CLASSIC WALKING-ONE PATTERN GIVES THE WRONG ANSWER. 0x01 under a rotate-right becomes
//      0x80, which is also its reversal. The module reports REVERSED for a rotation, and it reports
//      the ambiguity count 2 alongside. The bench asserts that the relation is WRONG, because writing
//      down that an instrument misleads under a named condition is the only honest way to publish it.

`timescale 1ns/1ps

module spi_slip_diag_tb;

    localparam int DW    = 32;
    localparam int LEN_W = 6;
    localparam int LEAD  = 4;
    localparam int HALF  = 3;
    localparam int LAG   = 2;
    localparam int GAP   = 4;

    localparam [2:0] X_IDENT = 3'd0, X_REV = 3'd1, X_ROTL = 3'd2,
                     X_ROTR  = 3'd3, X_SHL = 3'd4, X_SHR  = 3'd5, X_NONE = 3'd6;

    reg clk = 1'b0;
    always #5 clk = ~clk;
    reg rst_n = 1'b1;

    localparam [LEN_W-1:0] NB = 6'd8;

    reg              cpol = 1'b0, cpha = 1'b0;
    reg  [DW-1:0]    word_exp = {DW{1'b0}};
    reg  [DW-1:0]    word_rx  = {DW{1'b0}};
    reg              b_sclk = 1'b0, b_cs_n = 1'b1, b_mosi = 1'b0;

    wire [3:0]    pat_grade;
    wire          pat_pal, pat_msb0, pat_lsb0;
    wire          dg_valid;
    wire [DW-1:0] ob_word;
    wire [2:0]    rel_tx, rel_rx;
    wire [3:0]    amb_tx, amb_rx;

    spi_slip_diag #(.DW(DW), .LEN_W(LEN_W)) dut (
        .clk(clk), .rst_n(rst_n),
        .sclk(b_sclk), .cs_n(b_cs_n), .mosi(b_mosi),
        .cpol(cpol), .cpha(cpha), .len(NB),
        .word_exp(word_exp), .word_rx(word_rx),
        .pat_grade(pat_grade), .pat_pal(pat_pal), .pat_msb0(pat_msb0), .pat_lsb0(pat_lsb0),
        .dg_valid(dg_valid), .ob_word(ob_word),
        .rel_tx(rel_tx), .rel_rx(rel_rx), .amb_tx(amb_tx), .amb_rx(amb_rx)
    );

    integer errors = 0;
    integer x_reports = 0;
    integer got_n = 0;
    reg [2:0] g_rtx, g_rrx;
    reg [3:0] g_atx, g_arx;
    reg [DW-1:0] g_word;

    always @(posedge clk) if (dg_valid) begin
        got_n  = got_n + 1;
        g_rtx  = rel_tx; g_rrx = rel_rx;
        g_atx  = amb_tx; g_arx = amb_rx;
        g_word = ob_word;
        if ((^rel_tx === 1'bx) || (^rel_rx === 1'bx) || (^amb_tx === 1'bx)
            || (^amb_rx === 1'bx) || (^ob_word[7:0] === 1'bx))
            x_reports = x_reports + 1;
    end

    // THE INDEPENDENT ORACLE. The bench recomputes every transform from the definition rather than
    // asking the DUT, so a mistake in the DUT's indexing cannot hide behind the DUT's own arithmetic.
    // Chapter 16's rule, unchanged: measure with two implementations under one stimulus.
    function [DW-1:0] xfb(input integer k, input [DW-1:0] w);
        integer i;
        reg [DW-1:0] r;
        begin
            r = {DW{1'b0}};
            for (i = 0; i < NB; i = i + 1) begin
                case (k)
                    0: r[i] = w[i];
                    1: r[i] = w[NB-1-i];
                    2: r[i] = (i == 0)      ? w[NB-1] : w[i-1];
                    3: r[i] = (i == NB-1)   ? w[0]    : w[i+1];
                    4: r[i] = (i == 0)      ? 1'b0    : w[i-1];
                    5: r[i] = (i == NB-1)   ? 1'b0    : w[i+1];
                    default: r[i] = w[i];
                endcase
            end
            xfb = r;
        end
    endfunction

    function [8*6:1] xname(input [2:0] c);
        begin
            case (c)
                X_IDENT: xname = "IDENT ";
                X_REV:   xname = "REV   ";
                X_ROTL:  xname = "ROTL  ";
                X_ROTR:  xname = "ROTR  ";
                X_SHL:   xname = "SHL   ";
                X_SHR:   xname = "SHR   ";
                default: xname = "NONE  ";
            endcase
        end
    endfunction

    task automatic idle_n(input integer n);
        integer i;
        begin for (i = 0; i < n; i = i + 1) @(negedge clk); end
    endtask

    // Drive one frame. `w` is what goes on the wire -- the bench applies the master-side transform
    // itself, so the DUT is never handed a pre-digested answer. `extra` adds clock pulses after the
    // payload without adding data, which is what a clock-count error physically is.
    task automatic frame(input [DW-1:0] w, input integer extra);
        integer k;
        begin
            b_sclk = cpol; b_mosi = 1'b0;
            idle_n(2);
            b_cs_n = 1'b0;
            idle_n(1);
            b_mosi = w[NB-1];
            idle_n(LEAD - 1);
            for (k = 0; k < NB + extra; k = k + 1) begin
                b_sclk = ~b_sclk;
                idle_n(HALF);
                b_sclk = ~b_sclk;
                // After the last data bit MOSI rests at 0, which is the bit an extra pulse shifts
                // into the receiver -- so a clock-count error is a SHIFT and not a rotation, and the
                // difference is observable only when the payload's top bit is 1.
                b_mosi = (k < NB - 1) ? w[NB-1-k-1] : 1'b0;
                idle_n(HALF);
            end
            idle_n(LAG);
            b_cs_n = 1'b1;
            idle_n(1);
            b_sclk = cpol;
            idle_n(GAP);
        end
    endtask

    integer s, p, base;
    reg [DW-1:0] PAT [0:6];
    integer grade_log [0:6];
    integer bound_log [0:6];
    integer exact_n, strict_n;
    reg [DW-1:0] rx_log [0:6];
    reg [2:0] wtx, wrx;
    reg [3:0] watx, warx;
    reg [DW-1:0] wire_w;
    integer tx_xf, rx_xf, extra;
    integer mutations;

    initial begin
        PAT[0] = 32'h00; PAT[1] = 32'hFF; PAT[2] = 32'hAA; PAT[3] = 32'h55;
        PAT[4] = 32'h01; PAT[5] = 32'h0F; PAT[6] = 32'h8D;
        exact_n = 0; strict_n = 0; mutations = 0;

        rst_n = 1'b1; @(negedge clk); rst_n = 1'b0;
        repeat (4) @(negedge clk); rst_n = 1'b1; repeat (4) @(negedge clk);

        // ================= TABLE A -- grade the stimulus, with no stimulus =================
        $display("  pattern  grade  pal  msb0  lsb0  bound  verdict");
        for (p = 0; p < 7; p = p + 1) begin
            word_exp = PAT[p];
            #1;
            grade_log[p] = pat_grade;
            bound_log[p] = 6 - ({3'b0, pat_pal} + {3'b0, pat_msb0} + {3'b0, pat_lsb0});
            if (pat_grade > bound_log[p]) begin
                $display("  FAIL: 0x%02h graded %0d above its predicted bound %0d",
                         PAT[p][7:0], pat_grade, bound_log[p]);
                errors = errors + 1;
            end
            if (pat_grade == bound_log[p]) exact_n = exact_n + 1;
            else                           strict_n = strict_n + 1;
            $display("     0x%02h  %5d  %3b  %4b  %4b  %5d  %0s",
                     PAT[p][7:0], pat_grade, pat_pal, pat_msb0, pat_lsb0, bound_log[p],
                     (pat_grade == 1) ? "BLIND -- all six transforms give the same image" :
                     (pat_grade <  4) ? "weak -- at least three faults share a symptom" :
                     (pat_grade <  6) ? "usable -- one pair still collides" :
                                        "full -- every candidate fault is distinguishable");
        end
        if (grade_log[6] != 6) begin
            $display("  FAIL: 0x8d graded %0d where 6 was expected; the chapter's control pattern does not discriminate",
                     grade_log[6]);
            errors = errors + 1;
        end
        if (grade_log[0] != 1) begin
            $display("  FAIL: 0x00 graded %0d where 1 was expected", grade_log[0]);
            errors = errors + 1;
        end
        if (strict_n == 0) begin
            $display("  FAIL: every measured grade equalled its predicted bound, so the claim that the three rules are INCOMPLETE is unsupported");
            errors = errors + 1;
        end
        $display("    A. the three pencil rules -- palindrome hides reversal, top bit 0 merges rotate-left with shift-left, bottom bit 0 merges rotate-right with shift-right -- held as an upper bound for all seven payloads and were EXACT for only %0d of them. The other %0d lost more distinctness than the rules predict, because a payload with period 2 is carried to the same image by reversal AND by either rotation, which is a fourth mechanism the three rules do not express. The rules are sound and incomplete, and the grade has to be measured", exact_n, strict_n);

        // ================= TABLE B -- seven frames, two relations each =================
        $display("");
        $display("  pat  wire  rx   rel_tx  amb  rel_rx  amb  expected_tx/rx     stimulus");
        for (s = 0; s < 7; s = s + 1) begin
            @(negedge clk);
            b_sclk = cpol; b_cs_n = 1'b1; b_mosi = 1'b0;
            idle_n(2); rst_n = 1'b0; idle_n(3); rst_n = 1'b1; idle_n(2);
            base = got_n;

            tx_xf = 0; rx_xf = 0; extra = 0;
            case (s)
                // A correct link on the one payload that grades 6. Everything downstream depends on
                // this row: a decoder that has never been shown clean traffic has not been shown to
                // be silent.
                0: begin word_exp = 32'h8D; tx_xf = 0; wtx = X_IDENT; watx = 1; wrx = X_IDENT; warx = 1; end
                // The MASTER transmits LSB-first.
                1: begin word_exp = 32'h8D; tx_xf = 1; wtx = X_REV;   watx = 1; wrx = X_IDENT; warx = 1; end
                // The SLAVE receives LSB-first. Same reported byte as stimulus 1.
                2: begin word_exp = 32'h8D; rx_xf = 1; wtx = X_IDENT; watx = 1; wrx = X_REV;   warx = 1; end
                // ONE EXTRA CLOCK PULSE. The wire is clean over the payload's eight bits and the
                // receiver holds nine shifts' worth, so the fault is the master's and it presents as
                // a slave-side transform. Chapter 18.1's triage sees this one too -- 18 edges is not
                // 2N -- and two independent detectors agreeing on one fault is a feature.
                3: begin word_exp = 32'h8D; extra = 1; wtx = X_IDENT; watx = 1; wrx = X_SHL;   warx = 1; end
                // The right answer for the wrong reason: 0xAA's reversal, rotate-left and
                // rotate-right all give 0x55, so four transforms explain the pair.
                4: begin word_exp = 32'hAA; tx_xf = 1; wtx = X_REV;   watx = 4; wrx = X_IDENT; warx = 1; end
                // THE FALSE NEGATIVE. 0xFF reversed is 0xFF.
                5: begin word_exp = 32'hFF; tx_xf = 1; wtx = X_IDENT; watx = 4; wrx = X_IDENT; warx = 4; end
                // THE WALKING ONE, AND IT LIES. rotate-right of 0x01 is 0x80, which is its reversal.
                6: begin word_exp = 32'h01; tx_xf = 3; wtx = X_REV;   watx = 2; wrx = X_IDENT; warx = 1; end
            endcase

            wire_w = xfb(tx_xf, word_exp);
            // The receiver's own report, modelled by the bench: what it shifted in, then whatever it
            // does with it. An extra pulse means it shifted nb+1 times and holds the top nb of them.
            word_rx = xfb(rx_xf, (extra == 1) ? xfb(4, wire_w) : wire_w);
            rx_log[s] = word_rx;

            frame(wire_w, extra);

            $display("  0x%02h  0x%02h  0x%02h  %s  %3d  %s  %3d  %s/%s  %0s",
                     word_exp[7:0], g_word[7:0], word_rx[7:0],
                     xname(g_rtx), g_atx, xname(g_rrx), g_arx, xname(wtx), xname(wrx),
                     (s == 0) ? "a correct link, pattern 0x8d (grade 6)" :
                     (s == 1) ? "the MASTER transmits LSB-first" :
                     (s == 2) ? "the SLAVE receives LSB-first -- same byte as above" :
                     (s == 3) ? "ONE EXTRA clock pulse: clean wire, shifted receiver" :
                     (s == 4) ? "0xaa reversed: right answer, four hypotheses" :
                     (s == 5) ? "0xff reversed: a BROKEN link reports a clean byte" :
                                "0x01 rotated right: reported REVERSED -- wrong");

            if (got_n - base != 1) begin
                $display("  FAIL: stimulus %0d produced %0d diagnoses for one frame", s, got_n - base);
                errors = errors + 1;
            end
            if (g_rtx !== wtx || g_atx !== watx) begin
                $display("  FAIL: stimulus %0d reported rel_tx %s/%0d where %s/%0d was expected",
                         s, xname(g_rtx), g_atx, xname(wtx), watx);
                errors = errors + 1;
            end
            if (g_rrx !== wrx || g_arx !== warx) begin
                $display("  FAIL: stimulus %0d reported rel_rx %s/%0d where %s/%0d was expected",
                         s, xname(g_rrx), g_arx, xname(wrx), warx);
                errors = errors + 1;
            end
        end

        // ---- 1. a slip has a side, and only the wire reveals it ----
        if (rx_log[1] !== rx_log[2]) begin
            $display("  FAIL: the master-side and slave-side reversals reported different bytes (0x%02h, 0x%02h), so the claim that a byte cannot separate them was not exercised",
                     rx_log[1][7:0], rx_log[2][7:0]);
            errors = errors + 1;
        end
        $display("");
        $display("    1. the master-side reversal and the slave-side reversal BOTH reported 0x%02h against an expected 0x%02h. Identical bytes, identical mismatch report, identical error counter -- and opposite ends of the link. The wire split them on the first attempt, because the relation from the expectation to the WIRE is the master's contribution and the relation from the wire to the REPORT is the slave's, and a byte-level log has neither",
                 rx_log[1][7:0], 8'h8D);

        // ---- 3. the false negative ----
        if (rx_log[5] !== 8'hFF) begin
            $display("  FAIL: the 0xff reversal did not report the expected byte, so the false-negative demonstration did not occur");
            errors = errors + 1;
        end
        $display("    3. with pattern 0xff a master transmitting LSB-first put 0xff on the wire and the receiver reported 0xff. The expectation matched, the wire matched, the report matched, and the link was BROKEN. Nothing in the data disagreed with anything, so no comparison anywhere in a testbench or a driver could have fired. The only dissent available was the ambiguity count: %0d transforms explain the pair, so `nothing happened` was one hypothesis out of %0d rather than a finding",
                 4, 4);

        // ---- 4. the walking one lies, and that is written down ----
        if (g_rtx === X_ROTR) begin
            $display("  FAIL: the walking-one pattern correctly identified a rotation, which contradicts the chapter's claim that 0x01 cannot distinguish a rotation from a reversal");
            errors = errors + 1;
        end
        $display("    4. the last row is the instrument giving a WRONG answer under a named condition. The true fault was a rotate-right; 0x01 rotated right is 0x80, which is also 0x01 reversed, so the module reported %s. It is not a silent failure -- the ambiguity count read 2 -- but the headline verdict named the wrong transform, and 0x01 is the pattern bring-up scripts reach for first",
                 xname(X_REV));

        // ---- BENCH INTEGRITY ----
        // A PASS proves nothing about a bench unless the bench has been shown to fail. Two
        // deliberately wrong expectations, compared by the same operator as the real ones.
        if (grade_log[6] != 1) mutations = mutations + 1;
        if (rx_log[1] !== 8'h00) mutations = mutations + 1;
        if (mutations != 2) begin
            $display("  FAIL: a deliberately wrong expectation did not mismatch (%0d of 2)", mutations);
            errors = errors + 1;
        end
        if (x_reports != 0) begin
            $display("  FAIL: %0d reported fields carried X", x_reports);
            errors = errors + 1;
        end

        if (errors == 0) begin
            $display("");
            $display("    and the bench proved itself: two deliberately wrong expectations mismatched, and every reported field carried a known value");
            $display("PASS: an alignment fault has a SIDE and the wire is what factors it -- a master transmitting LSB-first and a slave receiving LSB-first reported the identical byte 0x%02h and were separated on the first capture, because the relation from expectation to wire belongs to the master and the relation from wire to report belongs to the slave. But whether either relation is decidable is a property of the PAYLOAD, not of the instrument: the six candidate transforms produce six distinct images for 0x8d, three for 0xaa and 0x55, and ONE for 0x00, where a capture says nothing at all. Three one-bit rules predict part of that -- a palindrome hides reversal, a top bit of 0 merges rotate-left with shift-left, a bottom bit of 0 does the same on the right -- and they held as an upper bound for all seven payloads while being exact for only %0d, because periodicity costs distinctness in a way the rules do not express. Two consequences are worth the whole chapter. With 0xff a reversed link reported the expected byte and every comparison in the system passed, so the fault was invisible to values and visible only as an ambiguity count of 4. And with 0x01 -- the walking-one pattern every bring-up script starts with -- a rotate-right was reported as a reversal, a wrong answer rather than a missing one. The pattern is part of the instrument, and choosing it by habit is choosing an instrument by habit",
                     rx_log[1][7:0], exact_n);
        end else begin
            $display("FAIL: %0d error(s)", errors);
        end
        $finish;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slip_diag_tb.v — the same bench in Verilog-2001
// spi_slip_diag_tb.v
//
// TWO TABLES, BECAUSE THERE ARE TWO MEASUREMENTS AND ONE OF THEM NEEDS NO STIMULUS AT ALL.
//
// TABLE A grades seven payloads. It drives no frames and needs no clock: the grade is combinational
// in the payload, which is the whole reason a bench can ask "can this pattern tell my candidate
// faults apart?" BEFORE it commits to a regression.
//
// TABLE B drives seven frames and asks, for each, what happened on each side of the pin.
//
// THE FOUR RESULTS.
//
//   1. A SLIP HAS A SIDE, AND ONLY THE WIRE REVEALS IT. A master transmitting LSB-first and a slave
//      receiving LSB-first report the IDENTICAL byte. The bench requires the two reported bytes to be
//      equal -- so the discrimination is a real result and not a restatement of the stimulus -- and
//      then splits them on `rel_tx` against `rel_rx`.
//
//   2. THE THREE PENCIL RULES ARE SOUND BUT NOT COMPLETE. A palindrome hides reversal, a top bit of 0
//      makes rotate-left and shift-left identical, a bottom bit of 0 does the same on the right. Those
//      three predict an UPPER BOUND on the grade. The measurement comes in at or below it for all
//      seven payloads and STRICTLY below for five of them, because a payload with period 2 is carried
//      to the same image by reversal and by either rotation -- a fourth mechanism the rules do not
//      express. A prediction that is checked against a measurement is worth more than either alone.
//
//   3. A BROKEN LINK REPORTS A CLEAN BYTE. With payload 0xFF a master that reverses the bit order
//      puts 0xFF on the wire and the receiver reports 0xFF. Every value in the system is correct. The
//      only thing that says otherwise is the AMBIGUITY COUNT -- four transforms explain the pair, so
//      "nothing happened" is one of four hypotheses rather than a finding.
//
//   4. THE CLASSIC WALKING-ONE PATTERN GIVES THE WRONG ANSWER. 0x01 under a rotate-right becomes
//      0x80, which is also its reversal. The module reports REVERSED for a rotation, and it reports
//      the ambiguity count 2 alongside. The bench asserts that the relation is WRONG, because writing
//      down that an instrument misleads under a named condition is the only honest way to publish it.

`timescale 1ns/1ps

module spi_slip_diag_tb;

    localparam DW    = 32;
    localparam LEN_W = 6;
    localparam LEAD  = 4;
    localparam HALF  = 3;
    localparam LAG   = 2;
    localparam GAP   = 4;

    localparam [2:0] X_IDENT = 3'd0, X_REV = 3'd1, X_ROTL = 3'd2,
                     X_ROTR  = 3'd3, X_SHL = 3'd4, X_SHR  = 3'd5, X_NONE = 3'd6;

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

    localparam [LEN_W-1:0] NB = 6'd8;

    reg              cpol, cpha;
    reg  [DW-1:0]    word_exp;
    reg  [DW-1:0]    word_rx;
    reg              b_sclk, b_cs_n, b_mosi;

    wire [3:0]    pat_grade;
    wire          pat_pal, pat_msb0, pat_lsb0;
    wire          dg_valid;
    wire [DW-1:0] ob_word;
    wire [2:0]    rel_tx, rel_rx;
    wire [3:0]    amb_tx, amb_rx;

    spi_slip_diag #(.DW(DW), .LEN_W(LEN_W)) dut (
        .clk(clk), .rst_n(rst_n),
        .sclk(b_sclk), .cs_n(b_cs_n), .mosi(b_mosi),
        .cpol(cpol), .cpha(cpha), .len(NB),
        .word_exp(word_exp), .word_rx(word_rx),
        .pat_grade(pat_grade), .pat_pal(pat_pal), .pat_msb0(pat_msb0), .pat_lsb0(pat_lsb0),
        .dg_valid(dg_valid), .ob_word(ob_word),
        .rel_tx(rel_tx), .rel_rx(rel_rx), .amb_tx(amb_tx), .amb_rx(amb_rx)
    );

    integer errors;
    integer x_reports;
    integer got_n;
    reg [2:0] g_rtx, g_rrx;
    reg [3:0] g_atx, g_arx;
    reg [DW-1:0] g_word;

    always @(posedge clk) if (dg_valid) begin
        got_n  = got_n + 1;
        g_rtx  = rel_tx; g_rrx = rel_rx;
        g_atx  = amb_tx; g_arx = amb_rx;
        g_word = ob_word;
        if ((^rel_tx === 1'bx) || (^rel_rx === 1'bx) || (^amb_tx === 1'bx)
            || (^amb_rx === 1'bx) || (^ob_word[7:0] === 1'bx))
            x_reports = x_reports + 1;
    end

    // THE INDEPENDENT ORACLE. The bench recomputes every transform from the definition rather than
    // asking the DUT, so a mistake in the DUT's indexing cannot hide behind the DUT's own arithmetic.
    // Chapter 16's rule, unchanged: measure with two implementations under one stimulus.
        function [DW-1:0] xfb;
        input integer k;
        input [DW-1:0] w;
        integer i;
        reg [DW-1:0] r;
        begin
            r = {DW{1'b0}};
            for (i = 0; i < NB; i = i + 1) begin
                case (k)
                    0: r[i] = w[i];
                    1: r[i] = w[NB-1-i];
                    2: r[i] = (i == 0)      ? w[NB-1] : w[i-1];
                    3: r[i] = (i == NB-1)   ? w[0]    : w[i+1];
                    4: r[i] = (i == 0)      ? 1'b0    : w[i-1];
                    5: r[i] = (i == NB-1)   ? 1'b0    : w[i+1];
                    default: r[i] = w[i];
                endcase
            end
            xfb = r;
        end
    endfunction

        function [8*6:1] xname;
        input [2:0] c;
        begin
            case (c)
                X_IDENT: xname = "IDENT ";
                X_REV:   xname = "REV   ";
                X_ROTL:  xname = "ROTL  ";
                X_ROTR:  xname = "ROTR  ";
                X_SHL:   xname = "SHL   ";
                X_SHR:   xname = "SHR   ";
                default: xname = "NONE  ";
            endcase
        end
    endfunction

        task idle_n;
        input integer n;
        integer i;
        begin for (i = 0; i < n; i = i + 1) @(negedge clk); end
    endtask

    // Drive one frame. `w` is what goes on the wire -- the bench applies the master-side transform
    // itself, so the DUT is never handed a pre-digested answer. `extra` adds clock pulses after the
    // payload without adding data, which is what a clock-count error physically is.
        task frame;
        input [DW-1:0] w;
        input integer extra;
        integer k;
        begin
            b_sclk = cpol; b_mosi = 1'b0;
            idle_n(2);
            b_cs_n = 1'b0;
            idle_n(1);
            b_mosi = w[NB-1];
            idle_n(LEAD - 1);
            for (k = 0; k < NB + extra; k = k + 1) begin
                b_sclk = ~b_sclk;
                idle_n(HALF);
                b_sclk = ~b_sclk;
                // After the last data bit MOSI rests at 0, which is the bit an extra pulse shifts
                // into the receiver -- so a clock-count error is a SHIFT and not a rotation, and the
                // difference is observable only when the payload's top bit is 1.
                b_mosi = (k < NB - 1) ? w[NB-1-k-1] : 1'b0;
                idle_n(HALF);
            end
            idle_n(LAG);
            b_cs_n = 1'b1;
            idle_n(1);
            b_sclk = cpol;
            idle_n(GAP);
        end
    endtask

    integer s, p, base;
    reg [DW-1:0] PAT [0:6];
    integer grade_log [0:6];
    integer bound_log [0:6];
    integer exact_n, strict_n;
    reg [DW-1:0] rx_log [0:6];
    reg [2:0] wtx, wrx;
    reg [3:0] watx, warx;
    reg [DW-1:0] wire_w;
    integer tx_xf, rx_xf, extra;
    integer mutations;

    initial begin
        PAT[0] = 32'h00; PAT[1] = 32'hFF; PAT[2] = 32'hAA; PAT[3] = 32'h55;
        PAT[4] = 32'h01; PAT[5] = 32'h0F; PAT[6] = 32'h8D;
        exact_n = 0; strict_n = 0; mutations = 0;

        rst_n = 1'b1; @(negedge clk); rst_n = 1'b0;
        repeat (4) @(negedge clk); rst_n = 1'b1; repeat (4) @(negedge clk);

        // ================= TABLE A -- grade the stimulus, with no stimulus =================
        $display("  pattern  grade  pal  msb0  lsb0  bound  verdict");
        for (p = 0; p < 7; p = p + 1) begin
            word_exp = PAT[p];
            #1;
            grade_log[p] = pat_grade;
            bound_log[p] = 6 - ({3'b0, pat_pal} + {3'b0, pat_msb0} + {3'b0, pat_lsb0});
            if (pat_grade > bound_log[p]) begin
                $display("  FAIL: 0x%02h graded %0d above its predicted bound %0d",
                         PAT[p][7:0], pat_grade, bound_log[p]);
                errors = errors + 1;
            end
            if (pat_grade == bound_log[p]) exact_n = exact_n + 1;
            else                           strict_n = strict_n + 1;
            $display("     0x%02h  %5d  %3b  %4b  %4b  %5d  %0s",
                     PAT[p][7:0], pat_grade, pat_pal, pat_msb0, pat_lsb0, bound_log[p],
                     (pat_grade == 1) ? "BLIND -- all six transforms give the same image" :
                     (pat_grade <  4) ? "weak -- at least three faults share a symptom" :
                     (pat_grade <  6) ? "usable -- one pair still collides" :
                                        "full -- every candidate fault is distinguishable");
        end
        if (grade_log[6] != 6) begin
            $display("  FAIL: 0x8d graded %0d where 6 was expected; the chapter's control pattern does not discriminate",
                     grade_log[6]);
            errors = errors + 1;
        end
        if (grade_log[0] != 1) begin
            $display("  FAIL: 0x00 graded %0d where 1 was expected", grade_log[0]);
            errors = errors + 1;
        end
        if (strict_n == 0) begin
            $display("  FAIL: every measured grade equalled its predicted bound, so the claim that the three rules are INCOMPLETE is unsupported");
            errors = errors + 1;
        end
        $display("    A. the three pencil rules -- palindrome hides reversal, top bit 0 merges rotate-left with shift-left, bottom bit 0 merges rotate-right with shift-right -- held as an upper bound for all seven payloads and were EXACT for only %0d of them. The other %0d lost more distinctness than the rules predict, because a payload with period 2 is carried to the same image by reversal AND by either rotation, which is a fourth mechanism the three rules do not express. The rules are sound and incomplete, and the grade has to be measured", exact_n, strict_n);

        // ================= TABLE B -- seven frames, two relations each =================
        $display("");
        $display("  pat  wire  rx   rel_tx  amb  rel_rx  amb  expected_tx/rx     stimulus");
        for (s = 0; s < 7; s = s + 1) begin
            @(negedge clk);
            b_sclk = cpol; b_cs_n = 1'b1; b_mosi = 1'b0;
            idle_n(2); rst_n = 1'b0; idle_n(3); rst_n = 1'b1; idle_n(2);
            base = got_n;

            tx_xf = 0; rx_xf = 0; extra = 0;
            case (s)
                // A correct link on the one payload that grades 6. Everything downstream depends on
                // this row: a decoder that has never been shown clean traffic has not been shown to
                // be silent.
                0: begin word_exp = 32'h8D; tx_xf = 0; wtx = X_IDENT; watx = 1; wrx = X_IDENT; warx = 1; end
                // The MASTER transmits LSB-first.
                1: begin word_exp = 32'h8D; tx_xf = 1; wtx = X_REV;   watx = 1; wrx = X_IDENT; warx = 1; end
                // The SLAVE receives LSB-first. Same reported byte as stimulus 1.
                2: begin word_exp = 32'h8D; rx_xf = 1; wtx = X_IDENT; watx = 1; wrx = X_REV;   warx = 1; end
                // ONE EXTRA CLOCK PULSE. The wire is clean over the payload's eight bits and the
                // receiver holds nine shifts' worth, so the fault is the master's and it presents as
                // a slave-side transform. Chapter 18.1's triage sees this one too -- 18 edges is not
                // 2N -- and two independent detectors agreeing on one fault is a feature.
                3: begin word_exp = 32'h8D; extra = 1; wtx = X_IDENT; watx = 1; wrx = X_SHL;   warx = 1; end
                // The right answer for the wrong reason: 0xAA's reversal, rotate-left and
                // rotate-right all give 0x55, so four transforms explain the pair.
                4: begin word_exp = 32'hAA; tx_xf = 1; wtx = X_REV;   watx = 4; wrx = X_IDENT; warx = 1; end
                // THE FALSE NEGATIVE. 0xFF reversed is 0xFF.
                5: begin word_exp = 32'hFF; tx_xf = 1; wtx = X_IDENT; watx = 4; wrx = X_IDENT; warx = 4; end
                // THE WALKING ONE, AND IT LIES. rotate-right of 0x01 is 0x80, which is its reversal.
                6: begin word_exp = 32'h01; tx_xf = 3; wtx = X_REV;   watx = 2; wrx = X_IDENT; warx = 1; end
            endcase

            wire_w = xfb(tx_xf, word_exp);
            // The receiver's own report, modelled by the bench: what it shifted in, then whatever it
            // does with it. An extra pulse means it shifted nb+1 times and holds the top nb of them.
            word_rx = xfb(rx_xf, (extra == 1) ? xfb(4, wire_w) : wire_w);
            rx_log[s] = word_rx;

            frame(wire_w, extra);

            $display("  0x%02h  0x%02h  0x%02h  %0s  %3d  %0s  %3d  %0s/%0s  %0s",
                     word_exp[7:0], g_word[7:0], word_rx[7:0],
                     xname(g_rtx), g_atx, xname(g_rrx), g_arx, xname(wtx), xname(wrx),
                     (s == 0) ? "a correct link, pattern 0x8d (grade 6)" :
                     (s == 1) ? "the MASTER transmits LSB-first" :
                     (s == 2) ? "the SLAVE receives LSB-first -- same byte as above" :
                     (s == 3) ? "ONE EXTRA clock pulse: clean wire, shifted receiver" :
                     (s == 4) ? "0xaa reversed: right answer, four hypotheses" :
                     (s == 5) ? "0xff reversed: a BROKEN link reports a clean byte" :
                                "0x01 rotated right: reported REVERSED -- wrong");

            if (got_n - base != 1) begin
                $display("  FAIL: stimulus %0d produced %0d diagnoses for one frame", s, got_n - base);
                errors = errors + 1;
            end
            if (g_rtx !== wtx || g_atx !== watx) begin
                $display("  FAIL: stimulus %0d reported rel_tx %0s/%0d where %0s/%0d was expected",
                         s, xname(g_rtx), g_atx, xname(wtx), watx);
                errors = errors + 1;
            end
            if (g_rrx !== wrx || g_arx !== warx) begin
                $display("  FAIL: stimulus %0d reported rel_rx %0s/%0d where %0s/%0d was expected",
                         s, xname(g_rrx), g_arx, xname(wrx), warx);
                errors = errors + 1;
            end
        end

        // ---- 1. a slip has a side, and only the wire reveals it ----
        if (rx_log[1] !== rx_log[2]) begin
            $display("  FAIL: the master-side and slave-side reversals reported different bytes (0x%02h, 0x%02h), so the claim that a byte cannot separate them was not exercised",
                     rx_log[1][7:0], rx_log[2][7:0]);
            errors = errors + 1;
        end
        $display("");
        $display("    1. the master-side reversal and the slave-side reversal BOTH reported 0x%02h against an expected 0x%02h. Identical bytes, identical mismatch report, identical error counter -- and opposite ends of the link. The wire split them on the first attempt, because the relation from the expectation to the WIRE is the master's contribution and the relation from the wire to the REPORT is the slave's, and a byte-level log has neither",
                 rx_log[1][7:0], 8'h8D);

        // ---- 3. the false negative ----
        if (rx_log[5] !== 8'hFF) begin
            $display("  FAIL: the 0xff reversal did not report the expected byte, so the false-negative demonstration did not occur");
            errors = errors + 1;
        end
        $display("    3. with pattern 0xff a master transmitting LSB-first put 0xff on the wire and the receiver reported 0xff. The expectation matched, the wire matched, the report matched, and the link was BROKEN. Nothing in the data disagreed with anything, so no comparison anywhere in a testbench or a driver could have fired. The only dissent available was the ambiguity count: %0d transforms explain the pair, so `nothing happened` was one hypothesis out of %0d rather than a finding",
                 4, 4);

        // ---- 4. the walking one lies, and that is written down ----
        if (g_rtx === X_ROTR) begin
            $display("  FAIL: the walking-one pattern correctly identified a rotation, which contradicts the chapter's claim that 0x01 cannot distinguish a rotation from a reversal");
            errors = errors + 1;
        end
        $display("    4. the last row is the instrument giving a WRONG answer under a named condition. The true fault was a rotate-right; 0x01 rotated right is 0x80, which is also 0x01 reversed, so the module reported %0s. It is not a silent failure -- the ambiguity count read 2 -- but the headline verdict named the wrong transform, and 0x01 is the pattern bring-up scripts reach for first",
                 xname(X_REV));

        // ---- BENCH INTEGRITY ----
        // A PASS proves nothing about a bench unless the bench has been shown to fail. Two
        // deliberately wrong expectations, compared by the same operator as the real ones.
        if (grade_log[6] != 1) mutations = mutations + 1;
        if (rx_log[1] !== 8'h00) mutations = mutations + 1;
        if (mutations != 2) begin
            $display("  FAIL: a deliberately wrong expectation did not mismatch (%0d of 2)", mutations);
            errors = errors + 1;
        end
        if (x_reports != 0) begin
            $display("  FAIL: %0d reported fields carried X", x_reports);
            errors = errors + 1;
        end

        if (errors == 0) begin
            $display("");
            $display("    and the bench proved itself: two deliberately wrong expectations mismatched, and every reported field carried a known value");
            $display("PASS: an alignment fault has a SIDE and the wire is what factors it -- a master transmitting LSB-first and a slave receiving LSB-first reported the identical byte 0x%02h and were separated on the first capture, because the relation from expectation to wire belongs to the master and the relation from wire to report belongs to the slave. But whether either relation is decidable is a property of the PAYLOAD, not of the instrument: the six candidate transforms produce six distinct images for 0x8d, three for 0xaa and 0x55, and ONE for 0x00, where a capture says nothing at all. Three one-bit rules predict part of that -- a palindrome hides reversal, a top bit of 0 merges rotate-left with shift-left, a bottom bit of 0 does the same on the right -- and they held as an upper bound for all seven payloads while being exact for only %0d, because periodicity costs distinctness in a way the rules do not express. Two consequences are worth the whole chapter. With 0xff a reversed link reported the expected byte and every comparison in the system passed, so the fault was invisible to values and visible only as an ambiguity count of 4. And with 0x01 -- the walking-one pattern every bring-up script starts with -- a rotate-right was reported as a reversal, a wrong answer rather than a missing one. The pattern is part of the instrument, and choosing it by habit is choosing an instrument by habit",
                     rx_log[1][7:0], exact_n);
        end else begin
            $display("FAIL: %0d error(s)", errors);
        end
        $finish;
    end


    initial begin
        cpol = 1'b0;
        cpha = 1'b0;
        b_sclk = 1'b0;
        b_cs_n = 1'b1;
        b_mosi = 1'b0;
        clk = 1'b0;
        rst_n = 1'b1;
        word_exp = {DW{1'b0}};
        word_rx = {DW{1'b0}};
        errors = 0;
        x_reports = 0;
        got_n = 0;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_slip_diag_tb.vhd — the same bench in VHDL
-- spi_slip_diag_tb.vhd
--
-- TWO TABLES, BECAUSE THERE ARE TWO MEASUREMENTS AND ONE OF THEM NEEDS NO STIMULUS AT ALL.
--
-- Table A grades seven payloads with no frames and no captures -- the grade is a pure function of the
-- payload, so a regression can ask whether its debug pattern is capable of separating its candidate
-- faults before it drives a single edge. Table B drives seven frames and factors each failure into
-- what the master did and what the slave did.
--
-- THE SAME FOUR RESULTS AS THE OTHER TWO LANGUAGES, AND THE SAME NUMBERS. The third implementation is
-- not ceremony: the VHDL port of Chapter 16.5 found a defect that two Verilog suites had agreed on,
-- and three independent spellings producing one table is the only available evidence that the table is
-- a property of the reasoning rather than of one simulator's scheduling.
--
-- THE ORACLE IS INDEPENDENT. `xfb` recomputes every transform from the definition instead of calling
-- the package the DUT uses, so an indexing mistake cannot hide behind its own arithmetic.
--
-- IDENTIFIER REVIEW (VHDL IS CASE-INSENSITIVE). `LEAD_C`, `HALF_C`, `LAG_C`, `GAP_C`, `NB_C` and
-- `DW_C` all carry the `_C` suffix so none of them can be shadowed by a signal, variable or
-- subprogram argument spelled the same way in another case. Re-read against that rule in full.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.spi_slip_pkg.all;

entity spi_slip_diag_tb is
end entity spi_slip_diag_tb;

architecture tb of spi_slip_diag_tb is

    constant LEAD_C : natural  := 4;
    constant HALF_C : natural  := 3;
    constant LAG_C  : natural  := 2;
    constant GAP_C  : natural  := 4;
    constant NB_C   : positive := 8;
    constant LEN_W  : positive := 6;

    signal clk   : std_logic := '0';
    signal rst_n : std_logic := '1';
    signal run   : boolean   := true;

    signal cpol : std_logic := '0';
    signal cpha : std_logic := '0';
    signal word_exp : std_logic_vector(DW_C - 1 downto 0) := (others => '0');
    signal word_rx  : std_logic_vector(DW_C - 1 downto 0) := (others => '0');

    signal b_sclk : std_logic := '0';
    signal b_cs_n : std_logic := '1';
    signal b_mosi : std_logic := '0';

    signal pat_grade : natural;
    signal pat_pal, pat_msb0, pat_lsb0 : boolean;
    signal dg_valid : std_logic;
    signal ob_word  : std_logic_vector(DW_C - 1 downto 0);
    signal rel_tx, rel_rx : slip_xf_t;
    signal amb_tx, amb_rx : natural;

    -- One driver for the capture, so no record or signal here can resolve to 'X' through two
    -- writers -- the structural rule adopted after Chapter 16.3.
    signal g_rtx, g_rrx : slip_xf_t := X_IDENT;
    signal g_atx, g_arx : natural   := 0;
    signal g_word : std_logic_vector(DW_C - 1 downto 0) := (others => '0');
    signal g_n    : natural := 0;
    signal g_x    : natural := 0;

    type pat_arr  is array (natural range <>) of std_logic_vector(DW_C - 1 downto 0);
    type nat_arr  is array (natural range <>) of natural;
    type xf_arr   is array (natural range <>) of slip_xf_t;

    constant PAT : pat_arr(0 to 6) :=
        (x"00000000", x"000000FF", x"000000AA", x"00000055",
         x"00000001", x"0000000F", x"0000008D");

    -- Right-justify an integer. `%-30s` is not portable across the three simulators -- one pads and one
    -- does not -- which is why every free-text column in this corpus sits at the END of a row and every
    -- numeric column is justified explicitly here.
    --
    -- THE LENGTH IS TAKEN FROM THE IMAGE, not assumed. The first version of this helper concatenated a
    -- fixed run of spaces onto `integer'image(v)` and declared the result a 24-character constant, which
    -- is a FATAL length mismatch the moment the value needs two digits -- and it survived two chapters
    -- only because every number they printed was a single digit. A constant whose length depends on its
    -- initialiser must be left unconstrained.
    function i2s (v : integer; w : natural) return string is
        constant S : string          := integer'image(v);
        constant P : string(1 to 40) := (others => ' ');
    begin
        if S'length >= w then return S; end if;
        return P(1 to w - S'length) & S;
    end function i2s;


    function b2s (b : boolean; w : natural) return string is
        constant P : string(1 to 24) := (others => ' ');
    begin
        if b then return P(1 to w - 1) & "1"; else return P(1 to w - 1) & "0"; end if;
    end function b2s;

    -- THE FORMAL IS CONSTRAINED, and it has to be. An unconstrained formal inherits the actual's index
    -- range, and a CONCATENATION produces an ascending `0 to n-1` range -- so `v(7 downto 0)` inside a
    -- function called with `x"00" & something` is a null slice and an index fault at run time, not a
    -- compile error. Writing the range into the declaration makes the indexing a property of this
    -- function rather than of how each caller spelled its argument.
    function hex8 (v : std_logic_vector(7 downto 0)) return string is
        constant D : string := "0123456789abcdef";
        variable r : string(1 to 2);
        variable u : natural := to_integer(unsigned(v));
    begin
        r(1) := D(u / 16 + 1);
        r(2) := D(u mod 16 + 1);
        return r;
    end function hex8;

    -- The bench's own transform, derived from the definition and not from the package under test.
    function xfb (k : slip_xf_t; w : std_logic_vector(DW_C - 1 downto 0))
        return std_logic_vector is
        variable r : std_logic_vector(DW_C - 1 downto 0) := (others => '0');
    begin
        for i in 0 to NB_C - 1 loop
            case k is
                when X_IDENT => r(i) := w(i);
                when X_REV   => r(i) := w(NB_C - 1 - i);
                when X_ROTL  => if i = 0        then r(i) := w(NB_C - 1); else r(i) := w(i - 1); end if;
                when X_ROTR  => if i = NB_C - 1 then r(i) := w(0);        else r(i) := w(i + 1); end if;
                when X_SHL   => if i = 0        then r(i) := '0';         else r(i) := w(i - 1); end if;
                when X_SHR   => if i = NB_C - 1 then r(i) := '0';         else r(i) := w(i + 1); end if;
                when others  => r(i) := w(i);
            end case;
        end loop;
        return r;
    end function xfb;

    function grade_text (g : natural) return string is
    begin
        if    g = 1 then return "BLIND -- all six transforms give the same image";
        elsif g < 4 then return "weak -- at least three faults share a symptom";
        elsif g < 6 then return "usable -- one pair still collides";
        else             return "full -- every candidate fault is distinguishable";
        end if;
    end function grade_text;

    function stim_text (s : natural) return string is
    begin
        case s is
            when 0 => return "a correct link, pattern 0x8d (grade 6)";
            when 1 => return "the MASTER transmits LSB-first";
            when 2 => return "the SLAVE receives LSB-first -- same byte as above";
            when 3 => return "ONE EXTRA clock pulse: clean wire, shifted receiver";
            when 4 => return "0xaa reversed: right answer, four hypotheses";
            when 5 => return "0xff reversed: a BROKEN link reports a clean byte";
            when others => return "0x01 rotated right: reported REVERSED -- wrong";
        end case;
    end function stim_text;

begin

    clk_gen : process is
    begin
        while run loop
            clk <= '0'; wait for 5 ns;
            clk <= '1'; wait for 5 ns;
        end loop;
        wait;
    end process clk_gen;

    dut : entity work.spi_slip_diag
        generic map (LEN_W => LEN_W)
        port map (
            clk => clk, rst_n => rst_n,
            sclk => b_sclk, cs_n => b_cs_n, mosi => b_mosi,
            cpol => cpol, cpha => cpha, len => to_unsigned(NB_C, LEN_W),
            word_exp => word_exp, word_rx => word_rx,
            pat_grade => pat_grade, pat_pal => pat_pal,
            pat_msb0 => pat_msb0, pat_lsb0 => pat_lsb0,
            dg_valid => dg_valid, ob_word => ob_word,
            rel_tx => rel_tx, rel_rx => rel_rx, amb_tx => amb_tx, amb_rx => amb_rx
        );

    cap : process (clk) is
    begin
        if rising_edge(clk) then
            if dg_valid = '1' then
                g_rtx <= rel_tx; g_rrx <= rel_rx;
                g_atx <= amb_tx; g_arx <= amb_rx;
                g_word <= ob_word;
                g_n <= g_n + 1;
                -- The enumeration and the naturals cannot hold a metavalue, so the only field that
                -- can is the captured word, and it is the only one guarded. A guard written where it
                -- cannot fire is theatre.
                for i in 0 to NB_C - 1 loop
                    if ob_word(i) /= '0' and ob_word(i) /= '1' then g_x <= g_x + 1; end if;
                end loop;
            end if;
        end if;
    end process cap;

    stim : process is

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

        -- `w` is CONSTRAINED, because an unconstrained formal inherits its index range from the
        -- actual and a bit-string literal carries an ASCENDING `0 to 31` range rather than the
        -- `31 downto 0` a reader assumes -- which silently indexes the payload from the wrong end.
        procedure frame (w : std_logic_vector(DW_C - 1 downto 0); extra : natural) is
        begin
            b_sclk <= cpol; b_mosi <= '0';
            idle_n(2);
            b_cs_n <= '0';
            idle_n(1);
            b_mosi <= w(NB_C - 1);
            idle_n(LEAD_C - 1);
            for k in 0 to NB_C - 1 + extra loop
                b_sclk <= not b_sclk;
                idle_n(HALF_C);
                b_sclk <= not b_sclk;
                -- After the last data bit MOSI rests at 0, and that is the bit an extra pulse shifts
                -- into the receiver. So a clock-count error is a SHIFT rather than a rotation, and
                -- the two are separable only when the payload's top bit is 1.
                if k < NB_C - 1 then b_mosi <= w(NB_C - 1 - k - 1); else b_mosi <= '0'; end if;
                idle_n(HALF_C);
            end loop;
            idle_n(LAG_C);
            b_cs_n <= '1';
            idle_n(1);
            b_sclk <= cpol;
            idle_n(GAP_C);
        end procedure frame;

        variable grade_log : nat_arr(0 to 6);
        variable bound_log : nat_arr(0 to 6);
        variable rx_log    : pat_arr(0 to 6);
        variable exact_n, strict_n, mutations, e, base, bound : natural := 0;
        variable wtx, wrx  : slip_xf_t;
        variable watx, warx : natural;
        variable tx_xf, rx_xf : slip_xf_t;
        variable extra     : natural;
        variable wire_w    : std_logic_vector(DW_C - 1 downto 0);
        variable ln        : line;

    begin
        rst_n <= '1';
        wait until falling_edge(clk);
        rst_n <= '0';
        idle_n(4);
        rst_n <= '1';
        idle_n(4);

        -- ================= TABLE A -- grade the stimulus, with no stimulus =================
        write(ln, string'("  pattern  grade  pal  msb0  lsb0  bound  verdict"));
        writeline(output, ln);
        for p in 0 to 6 loop
            word_exp <= PAT(p);
            wait for 1 ns;
            grade_log(p) := pat_grade;
            bound := 6;
            if pat_pal  then bound := bound - 1; end if;
            if pat_msb0 then bound := bound - 1; end if;
            if pat_lsb0 then bound := bound - 1; end if;
            bound_log(p) := bound;
            if pat_grade > bound then
                write(ln, string'("  FAIL: 0x") & hex8(PAT(p)(7 downto 0)) & string'(" graded ")
                          & i2s(pat_grade, 1) & string'(" above its predicted bound ") & i2s(bound, 1));
                writeline(output, ln); e := e + 1;
            end if;
            if pat_grade = bound then exact_n := exact_n + 1;
            else                      strict_n := strict_n + 1; end if;
            write(ln, string'("     0x") & hex8(PAT(p)(7 downto 0)) & string'("  ") & i2s(pat_grade, 5)
                      & string'("  ") & b2s(pat_pal, 3) & string'("  ") & b2s(pat_msb0, 4)
                      & string'("  ") & b2s(pat_lsb0, 4) & string'("  ") & i2s(bound, 5)
                      & string'("  ") & grade_text(pat_grade));
            writeline(output, ln);
        end loop;
        if grade_log(6) /= 6 then
            write(ln, string'("  FAIL: 0x8d graded ") & i2s(grade_log(6), 1)
                      & string'(" where 6 was expected; the chapter's control pattern does not discriminate"));
            writeline(output, ln); e := e + 1;
        end if;
        if grade_log(0) /= 1 then
            write(ln, string'("  FAIL: 0x00 graded ") & i2s(grade_log(0), 1) & string'(" where 1 was expected"));
            writeline(output, ln); e := e + 1;
        end if;
        if strict_n = 0 then
            write(ln, string'("  FAIL: every measured grade equalled its predicted bound, so the claim that the three rules are INCOMPLETE is unsupported"));
            writeline(output, ln); e := e + 1;
        end if;
        write(ln, string'("    A. the three pencil rules -- palindrome hides reversal, top bit 0 merges rotate-left with shift-left, bottom bit 0 merges rotate-right with shift-right -- held as an upper bound for all seven payloads and were EXACT for only ")
                  & i2s(exact_n, 1) & string'(" of them. The other ") & i2s(strict_n, 1)
                  & string'(" lost more distinctness than the rules predict, because a payload with period 2 is carried to the same image by reversal AND by either rotation, which is a fourth mechanism the three rules do not express. The rules are sound and incomplete, and the grade has to be measured"));
        writeline(output, ln);

        -- ================= TABLE B -- seven frames, two relations each =================
        write(ln, string'(""));
        writeline(output, ln);
        write(ln, string'("  pat  wire  rx   rel_tx  amb  rel_rx  amb  expected_tx/rx     stimulus"));
        writeline(output, ln);

        for s in 0 to 6 loop
            wait until falling_edge(clk);
            b_sclk <= cpol; b_cs_n <= '1'; b_mosi <= '0';
            idle_n(2); rst_n <= '0'; idle_n(3); rst_n <= '1'; idle_n(2);
            base := g_n;

            tx_xf := X_IDENT; rx_xf := X_IDENT; extra := 0;
            case s is
                when 0 => word_exp <= x"0000008D";
                          wtx := X_IDENT; watx := 1; wrx := X_IDENT; warx := 1;
                when 1 => word_exp <= x"0000008D"; tx_xf := X_REV;
                          wtx := X_REV;   watx := 1; wrx := X_IDENT; warx := 1;
                when 2 => word_exp <= x"0000008D"; rx_xf := X_REV;
                          wtx := X_IDENT; watx := 1; wrx := X_REV;   warx := 1;
                when 3 => word_exp <= x"0000008D"; extra := 1;
                          wtx := X_IDENT; watx := 1; wrx := X_SHL;   warx := 1;
                when 4 => word_exp <= x"000000AA"; tx_xf := X_REV;
                          wtx := X_REV;   watx := 4; wrx := X_IDENT; warx := 1;
                when 5 => word_exp <= x"000000FF"; tx_xf := X_REV;
                          wtx := X_IDENT; watx := 4; wrx := X_IDENT; warx := 4;
                when others => word_exp <= x"00000001"; tx_xf := X_ROTR;
                          wtx := X_REV;   watx := 2; wrx := X_IDENT; warx := 1;
            end case;
            wait for 1 ns;

            wire_w := xfb(tx_xf, word_exp);
            if extra = 1 then word_rx <= xfb(rx_xf, xfb(X_SHL, wire_w));
            else              word_rx <= xfb(rx_xf, wire_w);
            end if;
            wait for 1 ns;
            rx_log(s) := word_rx;

            frame(wire_w, extra);

            write(ln, string'("  0x") & hex8(word_exp(7 downto 0)) & string'("  0x") & hex8(g_word(7 downto 0))
                      & string'("  0x") & hex8(word_rx(7 downto 0)) & string'("  ") & xf_name(g_rtx)
                      & string'("  ") & i2s(g_atx, 3) & string'("  ") & xf_name(g_rrx)
                      & string'("  ") & i2s(g_arx, 3) & string'("  ") & xf_name(wtx)
                      & string'("/") & xf_name(wrx) & string'("  ") & stim_text(s));
            writeline(output, ln);

            if g_n - base /= 1 then
                write(ln, string'("  FAIL: stimulus ") & i2s(s, 1) & string'(" produced ")
                          & i2s(g_n - base, 1) & string'(" diagnoses for one frame"));
                writeline(output, ln); e := e + 1;
            end if;
            if g_rtx /= wtx or g_atx /= watx then
                write(ln, string'("  FAIL: stimulus ") & i2s(s, 1) & string'(" reported rel_tx ")
                          & xf_name(g_rtx) & string'("/") & i2s(g_atx, 1) & string'(" where ")
                          & xf_name(wtx) & string'("/") & i2s(watx, 1) & string'(" was expected"));
                writeline(output, ln); e := e + 1;
            end if;
            if g_rrx /= wrx or g_arx /= warx then
                write(ln, string'("  FAIL: stimulus ") & i2s(s, 1) & string'(" reported rel_rx ")
                          & xf_name(g_rrx) & string'("/") & i2s(g_arx, 1) & string'(" where ")
                          & xf_name(wrx) & string'("/") & i2s(warx, 1) & string'(" was expected"));
                writeline(output, ln); e := e + 1;
            end if;
        end loop;

        -- ---- 1. a slip has a side, and only the wire reveals it ----
        if rx_log(1) /= rx_log(2) then
            write(ln, string'("  FAIL: the master-side and slave-side reversals reported different bytes, so the claim that a byte cannot separate them was not exercised"));
            writeline(output, ln); e := e + 1;
        end if;
        write(ln, string'(""));
        writeline(output, ln);
        write(ln, string'("    1. the master-side reversal and the slave-side reversal BOTH reported 0x")
                  & hex8(rx_log(1)(7 downto 0))
                  & string'(" against an expected 0x8d. Identical bytes, identical mismatch report, identical error counter -- and opposite ends of the link. The wire split them on the first attempt, because the relation from the expectation to the WIRE is the master's contribution and the relation from the wire to the REPORT is the slave's, and a byte-level log has neither"));
        writeline(output, ln);

        -- ---- 3. the false negative ----
        if rx_log(5) /= x"000000FF" then
            write(ln, string'("  FAIL: the 0xff reversal did not report the expected byte, so the false-negative demonstration did not occur"));
            writeline(output, ln); e := e + 1;
        end if;
        write(ln, string'("    3. with pattern 0xff a master transmitting LSB-first put 0xff on the wire and the receiver reported 0xff. The expectation matched, the wire matched, the report matched, and the link was BROKEN. Nothing in the data disagreed with anything, so no comparison anywhere in a testbench or a driver could have fired. The only dissent available was the ambiguity count: 4 transforms explain the pair, so `nothing happened` was one hypothesis out of 4 rather than a finding"));
        writeline(output, ln);

        -- ---- 4. the walking one lies, and that is written down ----
        if g_rtx = X_ROTR then
            write(ln, string'("  FAIL: the walking-one pattern correctly identified a rotation, which contradicts the chapter's claim that 0x01 cannot distinguish a rotation from a reversal"));
            writeline(output, ln); e := e + 1;
        end if;
        write(ln, string'("    4. the last row is the instrument giving a WRONG answer under a named condition. The true fault was a rotate-right; 0x01 rotated right is 0x80, which is also 0x01 reversed, so the module reported ")
                  & xf_name(X_REV)
                  & string'(". It is not a silent failure -- the ambiguity count read 2 -- but the headline verdict named the wrong transform, and 0x01 is the pattern bring-up scripts reach for first"));
        writeline(output, ln);

        -- ---- BENCH INTEGRITY ----
        if grade_log(6) /= 1 then mutations := mutations + 1; end if;
        if rx_log(1) /= x"00000000" then mutations := mutations + 1; end if;
        if mutations /= 2 then
            write(ln, string'("  FAIL: a deliberately wrong expectation did not mismatch (")
                      & i2s(mutations, 1) & string'(" of 2)"));
            writeline(output, ln); e := e + 1;
        end if;
        if g_x /= 0 then
            write(ln, string'("  FAIL: ") & i2s(g_x, 1) & string'(" reported words carried a metavalue"));
            writeline(output, ln); e := e + 1;
        end if;

        if e = 0 then
            write(ln, string'(""));
            writeline(output, ln);
            write(ln, string'("    and the bench proved itself: two deliberately wrong expectations mismatched, and every reported field carried a known value"));
            writeline(output, ln);
            write(ln, string'("PASS: an alignment fault has a SIDE and the wire is what factors it -- a master transmitting LSB-first and a slave receiving LSB-first reported the identical byte 0x")
                      & hex8(rx_log(1)(7 downto 0))
                      & string'(" and were separated on the first capture, because the relation from expectation to wire belongs to the master and the relation from wire to report belongs to the slave. But whether either relation is decidable is a property of the PAYLOAD, not of the instrument: the six candidate transforms produce six distinct images for 0x8d, three for 0xaa and 0x55, and ONE for 0x00, where a capture says nothing at all. Three one-bit rules predict part of that -- a palindrome hides reversal, a top bit of 0 merges rotate-left with shift-left, a bottom bit of 0 does the same on the right -- and they held as an upper bound for all seven payloads while being exact for only ")
                      & i2s(exact_n, 1)
                      & string'(", because periodicity costs distinctness in a way the rules do not express. Two consequences are worth the whole chapter. With 0xff a reversed link reported the expected byte and every comparison in the system passed, so the fault was invisible to values and visible only as an ambiguity count of 4. And with 0x01 -- the walking-one pattern every bring-up script starts with -- a rotate-right was reported as a reversal, a wrong answer rather than a missing one. The pattern is part of the instrument, and choosing it by habit is choosing an instrument by habit"));
            writeline(output, ln);
        else
            write(ln, string'("FAIL: ") & i2s(e, 1) & string'(" error(s)"));
            writeline(output, ln);
        end if;

        run <= false;
        wait;
    end process stim;

end architecture tb;

10. What It Costs, Honestly

A chapter that presents a diagnostic as free is selling something.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   6 transform images of word_exp          6 × N-bit permutation networks (wiring + one mux row)
   6 transform images of the captured word same again
   2 relation sets                          12 N-bit comparators
   1 grade                                  15 N-bit comparators

For N = 8 that is small. For N = 32 the grade's fifteen comparators are the dominant term, and there is an obvious mitigation the chapter's own structure suggests: the grade depends only on word_exp, so it does not need to be in the datapath at all. Compute it once in firmware when the pattern is chosen, or in the testbench before the regression starts, and leave only the two relation sets in hardware. That halves the logic and loses nothing, because a grade that changes per frame was never the interesting case.

11. Grading The Stimulus Before The Regression Runs

The UVM consequence of section 5 is not a new component. It is a constraint.

A random-payload sequence that happens to generate 0x00, 0xFF, 0xAA or 0x55 is generating captures that cannot diagnose the faults the environment exists to find. So the grade belongs in the sequence item:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   class spi_slip_item extends uvm_sequence_item;
     rand bit [7:0] payload;
     // grade(payload) == 6  --  expressed as the three pencil rules, which are
     // cheap to constrain, plus a post_randomize check for the periodicity case
     constraint c_gradeable {
       payload[7] == 1'b1;            // rotate-left distinguishable from shift-left
       payload[0] == 1'b1;            // rotate-right distinguishable from shift-right
       payload != {<<{payload}};      // not a palindrome
     }
   endclass

12. What This Decoder Cannot Do

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ✗ diagnose anything when the payload's grade is 1 — every transform agrees
   ✗ distinguish a rotation from a clock-count error when the relevant end bit is 0
   ✗ detect a slip of MORE than one bit position (the candidate set stops at one)
   ✗ separate a master-side and a slave-side instance of the SAME transform when the
     two compose to the identity — two reversals in series look like a correct link

That last one deserves its own sentence, because it is the only limitation here that the wire cannot fix. A master transmitting LSB-first into a slave receiving LSB-first is a working link built from two faults, and it reports IDENT/IDENT because it genuinely is the identity end to end. The capture is not wrong; the link works. It will stop working the day either end is replaced by a correct one, which is the kind of latent defect that ships.

13. Why an FPGA Engineer Cares

Bit-order and clock-count faults dominate first bring-up of any SPI peripheral, and the usual instrument is a walking-one pattern. Section 8 shows that instrument giving a wrong answer.

The practical takeaway costs nothing to adopt: change the bring-up pattern from 0x01 or 0xAA to something with a grade of 6, and the same waveform you were already capturing becomes able to tell a rotation from a reversal from a miscounted clock. That is an instrument upgrade achieved by editing a constant.

The grade logic itself does not need to be on the FPGA. Compute it once, in a script, for the patterns your bring-up uses, and keep the two relation sets in logic if you want an automatic answer on a board you cannot probe.

14. Why an ASIC Engineer Cares

Section 12's last item is the one that reaches silicon. A master and a slave with matching bit-order faults is an interoperable pair that passes every test you run against it — and fails against any conformant third-party device. It is not detectable from the pins, because the composition genuinely is the identity, and it is not detectable from the data, because the data is correct. The only thing that finds it is a comparison against the specification's bit order, which means someone has to check rel_tx against the spec rather than against the other end of the link.

The clock-count case matters for a different reason: an extra pulse is a violation your own triage already sees as an edge-count error, so it costs nothing to detect and it is a routine consequence of a divider or a state machine that counts transitions instead of bit-times. Catching it before tape-out is cheap; catching it afterwards means a firmware workaround that every future driver has to know about.

15. Failure Signature — "It Works With 0xAA"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   Symptom     an SPI EEPROM read returns plausible but wrong data
   Test        write 0xAA, read back 0xAA, declare the link good
   Reality     the master transmits LSB-first; 0xAA reversed is 0x55, and
               the EEPROM's write path reversed it back on the way out
   Filed as    "link verified, suspect EEPROM contents"
   Found       three weeks later, by a colleague who used 0x8d

The test was not badly executed. It was executed with an instrument whose grade is 3, against a fault the instrument cannot see. And the conclusion it produced — the link is verified — was the most damaging output available, because it removed the link from the list of suspects.

16. Common Misconceptions

MisconceptionWhat is actually true
"The bits are shifted" is a diagnosisIt is a symptom shared by at least six distinct faults
A mismatched byte tells you which device is wrongThe byte is a composition; only the wire factors it
0xAA is a good bus test patternIt grades 3 of 6: reversal and both rotations all give 0x55
0xFF proves the wires are connectedIt also lets a reversed link report a perfect result
A walking one isolates alignment faultsFor 0x01, reversal and rotate-right give the same value
A rotation and a clock-count error are the same thingThey differ in the bit shifted in — observable only when that bit is 1
A link that passes is a link built correctlyTwo matching faults compose to the identity and pass everything

17. Reason It Through

18. Understanding Check

19. Summary

An alignment fault has a side, and the wire is what factors it: the relation from the expectation to the captured bits belongs to the master, the relation from those bits to the report belongs to the slave, and a master transmitting LSB-first and a slave receiving LSB-first produced the identical byte 0xb1 and were separated on the first capture. A byte-level mismatch report contains only the composition, and composition is exactly the information that loses which end did it.

But whether either relation is decidable is a property of the payload, not of the instrument. The six candidate transforms produce six distinct images for 0x8d, three for 0xAA and 0x55, and one for 0x00 — where a capture says nothing at all. Three one-bit rules predict part of that and held as an upper bound for all seven payloads while being exact for only two, because periodicity costs distinctness in a way the rules do not express; so the grade is measured rather than assumed.

Two consequences are worth the chapter on their own. With 0xFF a reversed link reported the expected byte and every comparison in the system passed, so the fault was invisible to values and visible only as an ambiguity count of four. And with 0x01 — the walking one every bring-up script starts with — a rotate-right was reported as a reversal: a wrong answer rather than a missing one. The pattern is part of the instrument.

20. What Comes Next

Alignment is settled, and the frame's length has not been questioned yet. Chapter 18.4 takes two faults that produce the same total edge count — a wrong transfer width and a wrong dummy-cycle count — and finds the discriminator in where the returned data starts rather than in how much of it there is.

Continue learning