Skip to content
VLSI Mentor

SPI · Module 18

Read Corruption and MISO Contention

Contention shows up as a level that is neither level. A marginal link and a structurally-early sampling instant share one verdict — and the only thing that separates them is slowing the clock down.

Every chapter so far has diagnosed by observing: a predicate, a transition time, a relation, a displacement, a count of assertions. This one cannot.

The discriminator here is not an observation. It is a change you make to the system.

1. Two Suspects, And One Of Them Is Not Digital

CauseNature
Contentiontwo devices driving MISO at once — a select decode that overlaps, or a slave that does not release the pin
Sampling marginthe slave's data is not valid at the instant the master samples it

The first is digital and countable. The second is where this chapter has to be careful, because most of it is not digital at all.

2. What RTL Can And Cannot Model — Said Up Front

This belongs here rather than in a footnote, because everything downstream depends on it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   CAN be modelled     a propagation delay measured against a sampling instant
                       that moves with the clock period. That is the arithmetic
                       of a setup margin: a bit is in time exactly when its
                       delay is no greater than the half period.

   CANNOT be modelled  intermittency. Temperature and voltage dependence.
                       Rise times, reflections, crosstalk, ground bounce.
                       A flip-flop resolving metastably either way.

The bench below models the first faithfully and the second not at all. So the error counts it produces are deterministic, and the thing they stand for is not: a real marginal link fails some of the time, more often when it is warm, and differently on each board.

3. Contention's Signature Is A Level, Not A Value

A contested net is not carrying the wrong bit. It is carrying neither bit — a mid-rail voltage that reads unpredictably. So the decoder counts indeterminate sampled bits separately from wrong ones, and that separation is what makes contention a finding rather than a guess.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ob_err     sampled bits that differ from the expectation
   ob_xbits   sampled bits that were neither 0 nor 1      ← contention only
   ob_overlap system-clock cycles with more than one select asserted

4. The Pair That Shares A Signature

Here is the chapter's real content, and it is not contention.

A bit that arrives late makes the master sample the previous bit — a stale sample. So does a master whose sampling instant is wrong by one bit by design. Two faults, in different places, needing different fixes, producing the same evidence:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   MARGINAL     the data arrives late relative to a sampling instant that is
                too close to it                    → fix the board, or slow down
   STRUCTURAL   the master samples at the wrong instant regardless of rate
                                                   → fix the RTL

Each bit arrives one cycle after the edge that samples it

14 cycles
Five rows over fourteen cycles. SCLK has a two-cycle half period, with leading edges at cycles zero, four, eight and twelve. The MISO row shows bit zero valid from cycle one, bit one from cycle five, bit two from cycle nine. A valid row marks those arrival instants and a sampled row shows the master reading bit zero correctly and then a stale value at each later edge.bit 0 — valid in timebit 0 — valid in timeevery later bit arrives lateevery later bit arrives lateedge 0 samples b0 — in timeedge 0 samples b0 — in timeb1 valid — one cycle too lateb1 valid — one cycle toolateb2 valid, edge 2 already read b1b2 valid, edge 2 alreadyread b1cs_nsclkmisoXb0b0b0b0b1b1b1b1b2b2b2b2b3validb0b0b0b0b1b1b1b1b2b2b2b2b3sampledokokokokstalestalestalestalestalestalestalestalestalestalet0t1t2t3t4t5t6t7t8t9t10t11t12t13
Figure 1 — a marginal link at a fast clock. Bit 0 sits on the pin before the first edge and is sampled correctly. Every later bit is launched at a trailing edge and needs three cycles to become valid, while the sampling edge arrives two cycles later — so each sample returns the previous bit. Horizontal scrolling is expected on a narrow screen; the figure is a timeline.

Now redraw that figure with a five-cycle half period instead of two. Every arrival lands comfortably before its edge and the errors vanish. Nothing about the figure's shape changes for the structural fault, because a slave that is one bit behind is one bit behind at any rate.

5. The First Bit Is The One Bit That Says Nothing

The measurement found something worth keeping. At the fastest rate the two faults report the same verdict and the same signature — every error a stale sample — and their error counts differ by exactly four in thirty-two. That difference is entirely the first bit of each frame.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   bit 0    has the whole select-to-first-edge LEAD to settle in
   bit 1..7 have only a half period

So a structurally-early slave gets bit 0 wrong too, and a marginal one does not. Which makes bit 0 the one bit in a frame that carries no information about marginal timing — and a four-in-thirty-two difference is not what anybody reads a verdict for.

6. The Discriminator Is An Intervention

There is no observation available at a single clock rate that separates the pair. The experiment has to change the system:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   slow the clock down       marginal   → errors fall, then vanish
                            structural → nothing moves

7. The Measurement

Four conditions at three clock rates, four frames each, identical output from all three languages:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  half    bits  errs    x  stale  1st  overlap   verdict       condition
       2     32      0     0      0      0        0   CLEAN         a correct link
       3     32      0     0      0      0        0   CLEAN         a correct link
       5     32      0     0      0      0        0   CLEAN         a correct link
       2     32     16    16      0      0       64   CONTENTION    CONTENTION -- a second select overlaps
       3     32     16    16      0      0       96   CONTENTION    CONTENTION -- a second select overlaps
       5     32     16    16      0      0      160   CONTENTION    CONTENTION -- a second select overlaps
       2     32     28     0     28      0        0   STALE         MARGINAL -- per-bit delays of 3, 4, 5
       3     32     20     0     20      0        0   STALE         MARGINAL -- per-bit delays of 3, 4, 5
       5     32      0     0      0      0        0   CLEAN         MARGINAL -- per-bit delays of 3, 4, 5
       2     32     32     0     32      4        0   STALE         STRUCTURAL -- the slave is one bit behind
       3     32     32     0     32      4        0   STALE         STRUCTURAL -- the slave is one bit behind
       5     32     32     0     32      4        0   STALE         STRUCTURAL -- the slave is one bit behind

The x column is exclusive to contention and the bench asserts it in both directions — non-zero for every contention run and zero for every other one. So indeterminate means contention is measured, not assumed.

Rows 7 and 10 are the pair. Same verdict, same all-stale signature, and the only visible difference is the 1st column: 0 against 4.

Rows 7–9 against rows 10–12 are the diagnosis. The marginal link goes 28, 20, 0 as the half-period goes 2, 3, 5. The structural fault sits at 32 throughout. Note that half is measured by the decoder from the pins rather than passed in — an error count without the rate it was taken at is not evidence of a trend, and the trend is the entire diagnosis.

The overlap column rises with the period for the contention runs — 64, 96, 160 — for the mundane reason that a slower clock makes every bit-time longer, so the same four-bit overlap window spans more system-clock cycles. That is a reminder to read accumulated counts against their denominator rather than against each other.

8. The Pattern, The Other Way Round

A stale sample returns the previous bit, so it is only visible when adjacent bits differ. Same physical fault, same clock rate, two payloads:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   0xaa   7 of 7 adjacent pairs differ    →  28 errors
   0xf0   1 of 7 differ                   →   4 errors

9. Building It — Three HDLs

Azvya Education Pvt. Ltd.VLSI Mentor
spi_miso_diag.sv — the MISO decoder — indeterminate bits, stale samples, and a verdict that refuses to name a cause
// spi_miso_diag.sv
//
// Chapter 18.6 -- read corruption on MISO, and the first diagnosis in this module that requires
// CHANGING the system rather than observing it.
//
// THE TWO CAUSES EVERYBODY SUSPECTS, and one of them is not digital.
//
//   CONTENTION        two devices driving MISO at once, because a select decode overlaps or because a
//                     slave does not release the pin when deselected. Digital, countable, and it has a
//                     signature no other fault has: the sampled bits are INDETERMINATE rather than
//                     wrong.
//
//   SAMPLING MARGIN   the slave's data is not valid at the instant the master samples it. This is where
//                     honesty is required, and section 11 of the chapter says it in full: RTL CANNOT
//                     MODEL THE PHYSICAL HALF OF THIS. What can be modelled is the TIMING half -- a
//                     propagation delay measured against a sampling instant that moves with the clock
//                     period -- and that is what the bench below does. What cannot be modelled is a
//                     real setup violation's intermittency, its temperature and voltage dependence,
//                     reflections, crosstalk, or a metastable flip-flop resolving either way. A
//                     simulation of marginal timing is deterministic; the thing it stands for is not.
//
// AND THE TWO THAT ACTUALLY NEED SEPARATING, which is the chapter's real content.
//
// A late-arriving bit makes the master sample the PREVIOUS bit -- a stale sample. So does a master
// whose sampling instant is structurally wrong by one bit. At any single clock rate the two produce
// IDENTICAL observations: the same error count, the same positions, the same stale signature.
//
//     MARGINAL    the data arrives late relative to a sampling instant that is too close to it
//                 -> SLOW THE CLOCK DOWN and the errors go away
//     STRUCTURAL  the master samples at the wrong instant by design
//                 -> slow the clock down and NOTHING CHANGES
//
// The discriminator is therefore not an observation at all. It is an INTERVENTION: change the clock
// period and measure the trend. This module supplies the per-run numbers; the trend across runs is the
// diagnosis, and it lives in whatever drives the experiment.
//
// That is the arc of this whole module arriving somewhere. Chapter 18.1 diagnosed from one capture.
// 18.4 needed two captures with different stimulus. This one needs two captures with a different
// SYSTEM, and there is no version of "look at the waveform more carefully" that substitutes for it.
//
// WHAT THIS MODULE PUBLISHES, per run of frames:
//
//     ob_frames, ob_bits      how much traffic the numbers are drawn from -- a rate needs a denominator
//     ob_err                  sampled bits differing from the expectation
//     ob_xbits                sampled bits that were neither 0 nor 1: the contention signature
//     ob_stale                errors whose sampled value equals the PREVIOUS expected bit
//     ob_err_first            errors in a frame's FIRST bit position: the output-enable signature
//     ob_overlap              system-clock cycles with more than one select asserted
//     ob_half                 the smallest SCLK half-period observed, in system-clock cycles
//
// `ob_half` exists so that a run's numbers carry the clock rate they were measured at. A table of
// error counts without the period beside each one is not evidence of a trend, and the trend is the
// entire diagnosis.

`timescale 1ns/1ps

module spi_miso_diag #(
    parameter int DW    = 32,
    parameter int NB    = 8,
    parameter int CNT_W = 16
) (
    input  wire              clk,
    input  wire              rst_n,

    input  wire              sclk,
    input  wire [1:0]        cs_n,     // two selects, so overlap is expressible
    input  wire              miso,

    input  wire              cpol,
    input  wire              cpha,
    input  wire [DW-1:0]     word_exp,

    input  wire              clr,      // start a new run of frames

    output reg  [CNT_W-1:0]  ob_frames,
    output reg  [CNT_W-1:0]  ob_bits,
    output reg  [CNT_W-1:0]  ob_err,
    output reg  [CNT_W-1:0]  ob_xbits,
    output reg  [CNT_W-1:0]  ob_stale,
    output reg  [CNT_W-1:0]  ob_err_first,
    output reg  [CNT_W-1:0]  ob_overlap,
    output reg  [CNT_W-1:0]  ob_half,
    output reg  [2:0]        dg_code
);

    localparam [2:0] D_CLEAN      = 3'd0,
                     // Indeterminate sampled bits with overlapping selects. The only fault here whose
                     // evidence is a LEVEL that is neither level.
                     D_CONTENTION = 3'd1,
                     // Every error is a stale sample. This verdict deliberately does NOT name a cause,
                     // because two causes produce it and one clock rate cannot separate them.
                     D_STALE      = 3'd2,
                     // Every error is in a frame's first bit: the slave's output driver turned on late.
                     D_ENABLE     = 3'd3,
                     D_OTHER      = 3'd4;

    reg             sclk_d;
    reg [1:0]       cs_n_d;
    reg             miso_rest;
    reg [CNT_W-1:0] nseen;
    reg [CNT_W-1:0] since_edge;

    wire cs0_assert   =  cs_n_d[0] & ~cs_n[0];
    wire cs0_deassert = ~cs_n_d[0] &  cs_n[0];
    wire in_txn       = ~cs_n[0] | cs0_deassert;
    wire sclk_edge    = (sclk !== sclk_d);
    wire leading      = sclk_edge && (sclk !== cpol);
    wire capture      = (cpha ? (sclk_edge && !leading) : leading) && in_txn;

    // More than one select asserted. A one-line observation that no amount of staring at MISO
    // substitutes for, because contention's effect on MISO is a voltage and its CAUSE is on other pins.
    wire overlap_now = (cs_n == 2'b00);

    // The bit the expectation says should be here, and the one before it. `prev` is what a stale sample
    // returns, and for the first bit of a frame there is no previous bit -- so the resting level of the
    // bus takes its place, which is exactly what a driver that has not turned on yet leaves behind.
    wire [CNT_W-1:0] idx  = nseen;
    wire             exp_now  = word_exp[NB - 1 - idx];
    wire             exp_prev = (idx == {CNT_W{1'b0}}) ? miso_rest : word_exp[NB - idx];

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sclk_d       <= 1'b0;
            cs_n_d       <= 2'b11;
            miso_rest    <= 1'b0;
            nseen        <= {CNT_W{1'b0}};
            since_edge   <= {CNT_W{1'b0}};
            ob_frames    <= {CNT_W{1'b0}};
            ob_bits      <= {CNT_W{1'b0}};
            ob_err       <= {CNT_W{1'b0}};
            ob_xbits     <= {CNT_W{1'b0}};
            ob_stale     <= {CNT_W{1'b0}};
            ob_err_first <= {CNT_W{1'b0}};
            ob_overlap   <= {CNT_W{1'b0}};
            ob_half      <= {CNT_W{1'b1}};
            dg_code      <= D_CLEAN;
        end else begin
            if (clr) begin
                ob_frames    <= {CNT_W{1'b0}};
                ob_bits      <= {CNT_W{1'b0}};
                ob_err       <= {CNT_W{1'b0}};
                ob_xbits     <= {CNT_W{1'b0}};
                ob_stale     <= {CNT_W{1'b0}};
                ob_err_first <= {CNT_W{1'b0}};
                ob_overlap   <= {CNT_W{1'b0}};
                ob_half      <= {CNT_W{1'b1}};
                nseen        <= {CNT_W{1'b0}};
            end else begin
                if (overlap_now) ob_overlap <= ob_overlap + 1'b1;

                // THE PERIOD MEASUREMENT. The smallest interval between SCLK edges seen in this run,
                // in system-clock cycles -- so every error count below carries the rate it was taken
                // at. Chapter 18.5's last exercise asked for exactly this observation and could not
                // supply it; a counting decoder has no notion of an interval.
                if (sclk_edge) begin
                    if (since_edge < ob_half) ob_half <= since_edge;
                    // Restarted at ONE, not zero. The interval between two edges H ticks apart
                    // contains H ticks, and a counter cleared to zero at the first of them reads
                    // H-1 at the second -- so every period in the report came back one short, and a
                    // half-period of 2 printed as 1. An off-by-one in a number that labels an
                    // experiment is worse than an off-by-one in the experiment.
                    since_edge <= {{(CNT_W-1){1'b0}}, 1'b1};
                end else begin
                    since_edge <= since_edge + 1'b1;
                end

                if (cs0_assert) begin
                    miso_rest <= miso;
                    nseen     <= {CNT_W{1'b0}};
                end else if (capture && (nseen < NB[CNT_W-1:0])) begin
                    ob_bits <= ob_bits + 1'b1;
                    if (miso !== exp_now) begin
                        ob_err <= ob_err + 1'b1;
                        // AN INDETERMINATE BIT IS NOT A WRONG BIT, and keeping them in separate
                        // counters is the difference between naming contention and guessing at it.
                        if ((miso !== 1'b0) && (miso !== 1'b1))
                            ob_xbits <= ob_xbits + 1'b1;
                        else if (miso === exp_prev)
                            ob_stale <= ob_stale + 1'b1;
                        if (idx == {CNT_W{1'b0}})
                            ob_err_first <= ob_err_first + 1'b1;
                    end
                    nseen <= nseen + 1'b1;
                end

                if (cs0_deassert) ob_frames <= ob_frames + 1'b1;

                // THE VERDICT IS FOR A RUN, NOT A FRAME, and it names what the evidence supports and
                // nothing more. `D_STALE` in particular is a deliberate refusal: two causes -- data
                // arriving late, and a sampling instant that is wrong by design -- produce identical
                // numbers at one clock rate, and the module does not pretend to choose between them.
                if (ob_overlap != {CNT_W{1'b0}})
                    dg_code <= D_CONTENTION;
                else if (ob_err == {CNT_W{1'b0}})
                    dg_code <= D_CLEAN;
                else if (ob_err == ob_err_first)
                    dg_code <= D_ENABLE;
                else if (ob_err == ob_stale)
                    // NOT `ob_stale + ob_err_first`. A first-bit error IS a stale sample -- the value
                    // returned is the bus's resting level, which is what `exp_prev` means at index 0 --
                    // so it is counted in BOTH `ob_stale` and `ob_err_first`, and adding them
                    // double-counts. The first version of this line did, and a structurally-early
                    // slave whose every error was stale fell through to D_OTHER: a verdict of
                    // `I cannot explain this` for the case the chapter is about.
                    dg_code <= D_STALE;
                else
                    dg_code <= D_OTHER;
            end

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

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_miso_diag.v — the same design in Verilog-2001
// spi_miso_diag.v
//
// Chapter 18.6 -- read corruption on MISO, and the first diagnosis in this module that requires
// CHANGING the system rather than observing it.
//
// THE TWO CAUSES EVERYBODY SUSPECTS, and one of them is not digital.
//
//   CONTENTION        two devices driving MISO at once, because a select decode overlaps or because a
//                     slave does not release the pin when deselected. Digital, countable, and it has a
//                     signature no other fault has: the sampled bits are INDETERMINATE rather than
//                     wrong.
//
//   SAMPLING MARGIN   the slave's data is not valid at the instant the master samples it. This is where
//                     honesty is required, and section 11 of the chapter says it in full: RTL CANNOT
//                     MODEL THE PHYSICAL HALF OF THIS. What can be modelled is the TIMING half -- a
//                     propagation delay measured against a sampling instant that moves with the clock
//                     period -- and that is what the bench below does. What cannot be modelled is a
//                     real setup violation's intermittency, its temperature and voltage dependence,
//                     reflections, crosstalk, or a metastable flip-flop resolving either way. A
//                     simulation of marginal timing is deterministic; the thing it stands for is not.
//
// AND THE TWO THAT ACTUALLY NEED SEPARATING, which is the chapter's real content.
//
// A late-arriving bit makes the master sample the PREVIOUS bit -- a stale sample. So does a master
// whose sampling instant is structurally wrong by one bit. At any single clock rate the two produce
// IDENTICAL observations: the same error count, the same positions, the same stale signature.
//
//     MARGINAL    the data arrives late relative to a sampling instant that is too close to it
//                 -> SLOW THE CLOCK DOWN and the errors go away
//     STRUCTURAL  the master samples at the wrong instant by design
//                 -> slow the clock down and NOTHING CHANGES
//
// The discriminator is therefore not an observation at all. It is an INTERVENTION: change the clock
// period and measure the trend. This module supplies the per-run numbers; the trend across runs is the
// diagnosis, and it lives in whatever drives the experiment.
//
// That is the arc of this whole module arriving somewhere. Chapter 18.1 diagnosed from one capture.
// 18.4 needed two captures with different stimulus. This one needs two captures with a different
// SYSTEM, and there is no version of "look at the waveform more carefully" that substitutes for it.
//
// WHAT THIS MODULE PUBLISHES, per run of frames:
//
//     ob_frames, ob_bits      how much traffic the numbers are drawn from -- a rate needs a denominator
//     ob_err                  sampled bits differing from the expectation
//     ob_xbits                sampled bits that were neither 0 nor 1: the contention signature
//     ob_stale                errors whose sampled value equals the PREVIOUS expected bit
//     ob_err_first            errors in a frame's FIRST bit position: the output-enable signature
//     ob_overlap              system-clock cycles with more than one select asserted
//     ob_half                 the smallest SCLK half-period observed, in system-clock cycles
//
// `ob_half` exists so that a run's numbers carry the clock rate they were measured at. A table of
// error counts without the period beside each one is not evidence of a trend, and the trend is the
// entire diagnosis.

`timescale 1ns/1ps

module spi_miso_diag #(
    parameter DW    = 32,
    parameter NB    = 8,
    parameter CNT_W = 16
) (
    input  wire              clk,
    input  wire              rst_n,

    input  wire              sclk,
    input  wire [1:0]        cs_n,     // two selects, so overlap is expressible
    input  wire              miso,

    input  wire              cpol,
    input  wire              cpha,
    input  wire [DW-1:0]     word_exp,

    input  wire              clr,      // start a new run of frames

    output reg  [CNT_W-1:0]  ob_frames,
    output reg  [CNT_W-1:0]  ob_bits,
    output reg  [CNT_W-1:0]  ob_err,
    output reg  [CNT_W-1:0]  ob_xbits,
    output reg  [CNT_W-1:0]  ob_stale,
    output reg  [CNT_W-1:0]  ob_err_first,
    output reg  [CNT_W-1:0]  ob_overlap,
    output reg  [CNT_W-1:0]  ob_half,
    output reg  [2:0]        dg_code
);

    localparam [2:0] D_CLEAN      = 3'd0,
                     // Indeterminate sampled bits with overlapping selects. The only fault here whose
                     // evidence is a LEVEL that is neither level.
                     D_CONTENTION = 3'd1,
                     // Every error is a stale sample. This verdict deliberately does NOT name a cause,
                     // because two causes produce it and one clock rate cannot separate them.
                     D_STALE      = 3'd2,
                     // Every error is in a frame's first bit: the slave's output driver turned on late.
                     D_ENABLE     = 3'd3,
                     D_OTHER      = 3'd4;

    reg             sclk_d;
    reg [1:0]       cs_n_d;
    reg             miso_rest;
    reg [CNT_W-1:0] nseen;
    reg [CNT_W-1:0] since_edge;

    wire cs0_assert   =  cs_n_d[0] & ~cs_n[0];
    wire cs0_deassert = ~cs_n_d[0] &  cs_n[0];
    wire in_txn       = ~cs_n[0] | cs0_deassert;
    wire sclk_edge    = (sclk !== sclk_d);
    wire leading      = sclk_edge && (sclk !== cpol);
    wire capture      = (cpha ? (sclk_edge && !leading) : leading) && in_txn;

    // More than one select asserted. A one-line observation that no amount of staring at MISO
    // substitutes for, because contention's effect on MISO is a voltage and its CAUSE is on other pins.
    wire overlap_now = (cs_n == 2'b00);

    // The bit the expectation says should be here, and the one before it. `prev` is what a stale sample
    // returns, and for the first bit of a frame there is no previous bit -- so the resting level of the
    // bus takes its place, which is exactly what a driver that has not turned on yet leaves behind.
    wire [CNT_W-1:0] idx  = nseen;
    wire             exp_now  = word_exp[NB - 1 - idx];
    wire             exp_prev = (idx == {CNT_W{1'b0}}) ? miso_rest : word_exp[NB - idx];

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sclk_d       <= 1'b0;
            cs_n_d       <= 2'b11;
            miso_rest    <= 1'b0;
            nseen        <= {CNT_W{1'b0}};
            since_edge   <= {CNT_W{1'b0}};
            ob_frames    <= {CNT_W{1'b0}};
            ob_bits      <= {CNT_W{1'b0}};
            ob_err       <= {CNT_W{1'b0}};
            ob_xbits     <= {CNT_W{1'b0}};
            ob_stale     <= {CNT_W{1'b0}};
            ob_err_first <= {CNT_W{1'b0}};
            ob_overlap   <= {CNT_W{1'b0}};
            ob_half      <= {CNT_W{1'b1}};
            dg_code      <= D_CLEAN;
        end else begin
            if (clr) begin
                ob_frames    <= {CNT_W{1'b0}};
                ob_bits      <= {CNT_W{1'b0}};
                ob_err       <= {CNT_W{1'b0}};
                ob_xbits     <= {CNT_W{1'b0}};
                ob_stale     <= {CNT_W{1'b0}};
                ob_err_first <= {CNT_W{1'b0}};
                ob_overlap   <= {CNT_W{1'b0}};
                ob_half      <= {CNT_W{1'b1}};
                nseen        <= {CNT_W{1'b0}};
            end else begin
                if (overlap_now) ob_overlap <= ob_overlap + 1'b1;

                // THE PERIOD MEASUREMENT. The smallest interval between SCLK edges seen in this run,
                // in system-clock cycles -- so every error count below carries the rate it was taken
                // at. Chapter 18.5's last exercise asked for exactly this observation and could not
                // supply it; a counting decoder has no notion of an interval.
                if (sclk_edge) begin
                    if (since_edge < ob_half) ob_half <= since_edge;
                    // Restarted at ONE, not zero. The interval between two edges H ticks apart
                    // contains H ticks, and a counter cleared to zero at the first of them reads
                    // H-1 at the second -- so every period in the report came back one short, and a
                    // half-period of 2 printed as 1. An off-by-one in a number that labels an
                    // experiment is worse than an off-by-one in the experiment.
                    since_edge <= {{(CNT_W-1){1'b0}}, 1'b1};
                end else begin
                    since_edge <= since_edge + 1'b1;
                end

                if (cs0_assert) begin
                    miso_rest <= miso;
                    nseen     <= {CNT_W{1'b0}};
                end else if (capture && (nseen < NB[CNT_W-1:0])) begin
                    ob_bits <= ob_bits + 1'b1;
                    if (miso !== exp_now) begin
                        ob_err <= ob_err + 1'b1;
                        // AN INDETERMINATE BIT IS NOT A WRONG BIT, and keeping them in separate
                        // counters is the difference between naming contention and guessing at it.
                        if ((miso !== 1'b0) && (miso !== 1'b1))
                            ob_xbits <= ob_xbits + 1'b1;
                        else if (miso === exp_prev)
                            ob_stale <= ob_stale + 1'b1;
                        if (idx == {CNT_W{1'b0}})
                            ob_err_first <= ob_err_first + 1'b1;
                    end
                    nseen <= nseen + 1'b1;
                end

                if (cs0_deassert) ob_frames <= ob_frames + 1'b1;

                // THE VERDICT IS FOR A RUN, NOT A FRAME, and it names what the evidence supports and
                // nothing more. `D_STALE` in particular is a deliberate refusal: two causes -- data
                // arriving late, and a sampling instant that is wrong by design -- produce identical
                // numbers at one clock rate, and the module does not pretend to choose between them.
                if (ob_overlap != {CNT_W{1'b0}})
                    dg_code <= D_CONTENTION;
                else if (ob_err == {CNT_W{1'b0}})
                    dg_code <= D_CLEAN;
                else if (ob_err == ob_err_first)
                    dg_code <= D_ENABLE;
                else if (ob_err == ob_stale)
                    // NOT `ob_stale + ob_err_first`. A first-bit error IS a stale sample -- the value
                    // returned is the bus's resting level, which is what `exp_prev` means at index 0 --
                    // so it is counted in BOTH `ob_stale` and `ob_err_first`, and adding them
                    // double-counts. The first version of this line did, and a structurally-early
                    // slave whose every error was stale fell through to D_OTHER: a verdict of
                    // `I cannot explain this` for the case the chapter is about.
                    dg_code <= D_STALE;
                else
                    dg_code <= D_OTHER;
            end

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

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_miso_diag.vhd — the same design in VHDL
-- spi_miso_diag.vhd
--
-- Chapter 18.6 -- read corruption on MISO, and the first diagnosis in this module that requires
-- CHANGING the system rather than observing it.
--
-- THE TWO CAUSES EVERYBODY SUSPECTS, and one of them is not digital.
--
--   CONTENTION        two devices driving MISO at once, because a select decode overlaps or because a
--                     slave does not release the pin when deselected. Digital, countable, and it has a
--                     signature no other fault has: the sampled bits are INDETERMINATE rather than
--                     wrong.
--
--   SAMPLING MARGIN   the slave's data is not valid at the instant the master samples it. This is where
--                     honesty is required, and section 11 of the chapter says it in full: RTL CANNOT
--                     MODEL THE PHYSICAL HALF OF THIS. What can be modelled is the TIMING half -- a
--                     propagation delay measured against a sampling instant that moves with the clock
--                     period -- and that is what the bench below does. What cannot be modelled is a
--                     real setup violation's intermittency, its temperature and voltage dependence,
--                     reflections, crosstalk, or a metastable flip-flop resolving either way. A
--                     simulation of marginal timing is deterministic; the thing it stands for is not.
--
-- AND THE TWO THAT ACTUALLY NEED SEPARATING, which is the chapter's real content.
--
-- A late-arriving bit makes the master sample the PREVIOUS bit -- a stale sample. So does a master
-- whose sampling instant is structurally wrong by one bit. At any single clock rate the two produce
-- IDENTICAL observations: the same error count, the same positions, the same stale signature.
--
--     MARGINAL    the data arrives late relative to a sampling instant that is too close to it
--                 -> SLOW THE CLOCK DOWN and the errors go away
--     STRUCTURAL  the master samples at the wrong instant by design
--                 -> slow the clock down and NOTHING CHANGES
--
-- The discriminator is therefore not an observation at all. It is an INTERVENTION: change the clock
-- period and measure the trend. This module supplies the per-run numbers; the trend across runs is the
-- diagnosis, and it lives in whatever drives the experiment.
--
-- That is the arc of this whole module arriving somewhere. Chapter 18.1 diagnosed from one capture.
-- 18.4 needed two captures with different stimulus. This one needs two captures with a different
-- SYSTEM, and there is no version of "look at the waveform more carefully" that substitutes for it.
--
-- WHAT THIS MODULE PUBLISHES, per run of frames:
--
--     ob_frames, ob_bits      how much traffic the numbers are drawn from -- a rate needs a denominator
--     ob_err                  sampled bits differing from the expectation
--     ob_xbits                sampled bits that were neither 0 nor 1: the contention signature
--     ob_stale                errors whose sampled value equals the PREVIOUS expected bit
--     ob_err_first            errors in a frame's FIRST bit position: the output-enable signature
--     ob_overlap              system-clock cycles with more than one select asserted
--     ob_half                 the smallest SCLK half-period observed, in system-clock cycles
--
-- `ob_half` exists so that a run's numbers carry the clock rate they were measured at. A table of
-- error counts without the period beside each one is not evidence of a trend, and the trend is the
-- entire diagnosis.

--
-- WHAT THE VHDL VERSION ADDS HERE IS UNUSUALLY CONCRETE, and it is the reason this chapter's third
-- language is more than a formality.
--
-- `std_logic` is a RESOLVED type. Two processes driving one signal do not produce an error; they produce
-- 'X' through the resolution function -- which is exactly what a contested net does. So in the VHDL
-- bench, contention is not injected by writing an 'X': it is created by CONNECTING A SECOND DRIVER, and
-- the 'X' that appears on MISO is the language's own resolution of two devices driving at once.
--
-- That is a materially better model than the other two languages get. The Verilog benches assign 1'bx
-- during the overlap window, which is a HAND-PLACED value standing in for a physical effect; the VHDL
-- bench places two drivers and lets the effect happen. If the overlap window were computed wrongly in
-- the Verilog benches the 'x' would still appear exactly where it was told to; here it appears only
-- where two drivers genuinely coincide.
--
-- IDENTIFIER REVIEW (VHDL IS CASE-INSENSITIVE). Generics are `NB_C` and `CNT_W`; the counters are
-- `n_frames`, `n_bits`, `n_err`, `n_x`, `n_stale`, `n_first`, `n_ovl`, `n_half`. None of them is
-- `NB`/`nb` or a case variant of any generic, port or subprogram argument -- the check Chapter 17.4
-- learned to run after a variable `tries` silently became the generic `TRIES`.

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

package spi_miso_pkg is

    constant DW_C : natural := 32;

    -- Five verdicts for a RUN of frames, not for a frame.
    --
    --   D_CONTENTION  indeterminate sampled bits with overlapping selects
    --   D_STALE       every error is a stale sample. A DELIBERATE REFUSAL to name a cause: two causes
    --                 produce this and one clock rate cannot separate them.
    --   D_ENABLE      every error is in a frame's first bit -- the output driver turned on late
    type miso_diag_t is (D_CLEAN, D_CONTENTION, D_STALE, D_ENABLE, D_OTHER);

    function diag_name (d : miso_diag_t) return string;

end package spi_miso_pkg;

package body spi_miso_pkg is
    function diag_name (d : miso_diag_t) return string is
    begin
        case d is
            when D_CLEAN      => return "CLEAN       ";
            when D_CONTENTION => return "CONTENTION  ";
            when D_STALE      => return "STALE       ";
            when D_ENABLE     => return "ENABLE      ";
            when others       => return "OTHER       ";
        end case;
    end function diag_name;
end package body spi_miso_pkg;

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

entity spi_miso_diag is
    generic (
        NB_C  : positive := 8;
        CNT_W : positive := 16
    );
    port (
        clk      : in  std_logic;
        rst_n    : in  std_logic;

        sclk     : in  std_logic;
        cs_n     : in  std_logic_vector(1 downto 0);   -- two selects, so overlap is expressible
        miso     : in  std_logic;

        cpol     : in  std_logic;
        cpha     : in  std_logic;
        word_exp : in  std_logic_vector(DW_C - 1 downto 0);

        clr      : in  std_logic;                      -- start a new run of frames

        ob_frames    : out natural;
        ob_bits      : out natural;
        ob_err       : out natural;
        ob_xbits     : out natural;
        ob_stale     : out natural;
        ob_err_first : out natural;
        ob_overlap   : out natural;
        ob_half      : out natural;
        dg_code      : out miso_diag_t
    );
end entity spi_miso_diag;

architecture rtl of spi_miso_diag is
    constant BIG_C : natural := 2 ** CNT_W - 1;

    signal f_r, b_r, e_r, x_r, s_r, fb_r, o_r, h_r : natural := 0;
    signal d_r : miso_diag_t := D_CLEAN;
begin

    ob_frames    <= f_r;
    ob_bits      <= b_r;
    ob_err       <= e_r;
    ob_xbits     <= x_r;
    ob_stale     <= s_r;
    ob_err_first <= fb_r;
    ob_overlap   <= o_r;
    ob_half      <= h_r;
    dg_code      <= d_r;

    process (clk, rst_n) is
        variable sclk_d    : std_logic;
        variable cs_n_d    : std_logic_vector(1 downto 0);
        variable miso_rest : std_logic;
        variable nseen     : natural;
        variable since     : natural;
        variable cs0_assert, cs0_deassert : boolean;
        variable in_txn, sclk_edge        : boolean;
        variable leading, capture         : boolean;
        variable exp_now, exp_prev        : std_logic;
        variable n_frames, n_bits, n_err  : natural;
        variable n_x, n_stale, n_first    : natural;
        variable n_ovl, n_half            : natural;
    begin
        if rst_n = '0' then
            sclk_d := '0'; cs_n_d := "11"; miso_rest := '0';
            nseen := 0; since := 0;
            n_frames := 0; n_bits := 0; n_err := 0; n_x := 0;
            n_stale := 0; n_first := 0; n_ovl := 0; n_half := BIG_C;
            f_r <= 0; b_r <= 0; e_r <= 0; x_r <= 0; s_r <= 0; fb_r <= 0; o_r <= 0;
            h_r <= BIG_C; d_r <= D_CLEAN;

        elsif rising_edge(clk) then
            if clr = '1' then
                n_frames := 0; n_bits := 0; n_err := 0; n_x := 0;
                n_stale := 0; n_first := 0; n_ovl := 0; n_half := BIG_C;
                nseen := 0;
            else
                -- More than one select asserted. A one-line observation that no amount of staring at
                -- MISO substitutes for, because contention's effect is on MISO and its CAUSE is on
                -- other pins entirely.
                if cs_n = "00" then n_ovl := n_ovl + 1; end if;

                cs0_assert   := (cs_n(0) = '0') and (cs_n_d(0) = '1');
                cs0_deassert := (cs_n(0) = '1') and (cs_n_d(0) = '0');
                in_txn       := (cs_n(0) = '0') or cs0_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;

                -- THE PERIOD MEASUREMENT, restarted at ONE rather than zero: the interval between two
                -- edges H ticks apart contains H ticks, and a counter cleared to zero at the first of
                -- them reads H-1 at the second. Every error count in the report carries the rate it was
                -- taken at, and an off-by-one in the number that LABELS an experiment is worse than one
                -- inside it.
                if sclk_edge then
                    if since < n_half then n_half := since; end if;
                    since := 1;
                else
                    since := since + 1;
                end if;

                if cs0_assert then
                    miso_rest := miso;
                    nseen     := 0;
                elsif capture and nseen < NB_C then
                    exp_now := word_exp(NB_C - 1 - nseen);
                    -- For the first bit of a frame there is no previous bit, so the bus's resting level
                    -- takes its place -- which is exactly what a driver that has not turned on yet
                    -- leaves behind.
                    if nseen = 0 then exp_prev := miso_rest;
                    else              exp_prev := word_exp(NB_C - nseen);
                    end if;

                    n_bits := n_bits + 1;
                    if miso /= exp_now then
                        n_err := n_err + 1;
                        -- AN INDETERMINATE BIT IS NOT A WRONG BIT, and separate counters are the
                        -- difference between naming contention and guessing at it.
                        if miso /= '0' and miso /= '1' then
                            n_x := n_x + 1;
                        elsif miso = exp_prev then
                            n_stale := n_stale + 1;
                        end if;
                        if nseen = 0 then n_first := n_first + 1; end if;
                    end if;
                    nseen := nseen + 1;
                end if;

                if cs0_deassert then n_frames := n_frames + 1; end if;
            end if;

            f_r <= n_frames; b_r <= n_bits;  e_r  <= n_err;   x_r  <= n_x;
            s_r <= n_stale;  fb_r <= n_first; o_r <= n_ovl;   h_r  <= n_half;

            -- THE VERDICT IS FOR A RUN, and it names what the evidence supports and nothing more.
            if n_ovl /= 0 then
                d_r <= D_CONTENTION;
            elsif n_err = 0 then
                d_r <= D_CLEAN;
            elsif n_err = n_first then
                d_r <= D_ENABLE;
            elsif n_err = n_stale then
                -- NOT `n_stale + n_first`. A first-bit error IS a stale sample -- the value returned is
                -- the bus's resting level, which is what `exp_prev` means at index 0 -- so it is counted
                -- in BOTH, and adding them double-counts. The first version of this line did, and a
                -- structurally-early slave whose every error was stale fell through to D_OTHER: a
                -- verdict of `I cannot explain this` for the case the chapter is about.
                d_r <= D_STALE;
            else
                d_r <= D_OTHER;
            end if;

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

end architecture rtl;

The Bench

The bench builds the MISO waveform as a table of per-tick values before driving anything, so the timing model is something a reader can check rather than behaviour emerging from the order of statements in a driver.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_miso_diag_tb.sv — four conditions at three clock rates, and two faults one verdict cannot tell apart
// spi_miso_diag_tb.sv
//
// FOUR CONDITIONS AT THREE CLOCK RATES, AND TWO FAULTS THAT ONE RATE CANNOT TELL APART.
//
// HOW THE TIMING IS MODELLED, stated before any result so that no reader has to guess how much of this
// is simulation and how much is physics.
//
// The bench BUILDS THE MISO WAVEFORM AS AN ARRAY OF PER-TICK VALUES before driving anything. Each bit
// becomes valid on the pin at
//
//     (the trailing edge that launched it) + d
//
// where `d` is a propagation delay in system-clock ticks. The master samples at the leading edge, one
// half-period later. So whether a bit is valid in time is a comparison between a FIXED delay and a
// half-period that the experiment varies -- which is exactly the arithmetic of a setup margin, and the
// reason slowing the clock fixes a marginal link.
//
// Building the waveform first rather than emitting it from a loop is deliberate: the timing model is
// then a table somebody can read and check, instead of behaviour that emerges from the order of
// statements in a driver.
//
// WHAT THIS DOES NOT MODEL, and the chapter repeats it where a reader cannot miss it: a real setup
// violation is intermittent, temperature- and voltage-dependent, and can leave a flip-flop metastable.
// This one is deterministic. The model is faithful about WHICH bits fail and why; it is silent about
// how often, and anything a reader concludes here about failure RATES is a property of the model.
//
// THE FOUR RESULTS.
//
//   1. CONTENTION HAS A SIGNATURE NO OTHER FAULT HAS: sampled bits that are neither 0 nor 1, together
//      with overlapping selects. The bench requires the indeterminate-bit count to be non-zero for
//      contention and ZERO for every other condition, so "indeterminate means contention" is measured
//      rather than assumed.
//
//   2. THE HEADLINE. A marginal link and a structurally-early sampling instant produce the SAME verdict
//      and the SAME signature -- every error a stale sample -- at any single rate. The bench requires
//      that, which is an assertion that a measurement FAILS to discriminate. Their error counts differ
//      by exactly the FIRST BIT of each frame, and the bench asserts that too: bit 0 has the whole
//      select-to-first-edge lead to settle in, so it is the one bit in a frame that says nothing about
//      marginal timing. Four counts in thirty-two is not what anybody reads a verdict for.
//
//   3. THE DISCRIMINATOR IS AN INTERVENTION. There is no observation at a single rate that separates
//      result 2's pair. The experiment has to change the system, which is a different kind of
//      diagnostic step from everything earlier in this module.
//
//   4. THE BEST DEBUG PATTERN IS THE OPPOSITE OF CHAPTER 18.3'S. A stale sample is only VISIBLE when
//      adjacent bits differ, so the same physical fault produces a large error count with 0xAA and a
//      small one with 0xF0. Chapter 18.3 measured 0xAA as one of the worst patterns for alignment
//      faults; here it is the best available. The pattern is part of the instrument in both chapters
//      and the right choice is the opposite one.

`timescale 1ns/1ps

module spi_miso_diag_tb;

    localparam int DW    = 32;
    localparam int NB    = 8;
    localparam int CNT_W = 16;
    localparam int LEAD  = 3;
    localparam int LAG   = 2;
    localparam int GAP   = 3;
    localparam int MAXT  = 512;

    localparam [2:0] D_CLEAN = 3'd0, D_CONTENTION = 3'd1, D_STALE = 3'd2,
                     D_ENABLE = 3'd3, D_OTHER = 3'd4;

    // The four conditions.
    localparam int C_CLEAN  = 0;
    localparam int C_CONT   = 1;   // a second select overlaps -- two drivers on MISO
    localparam int C_MARG   = 2;   // per-bit propagation delays of 3, 4 and 5 ticks
    localparam int C_EARLY  = 3;   // the slave is structurally one bit behind, at every rate

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

    reg          cpol = 1'b0, cpha = 1'b0;
    reg [DW-1:0] word_exp = {DW{1'b0}};
    reg          clr = 1'b0;

    reg       b_sclk = 1'b0;
    reg [1:0] b_cs_n = 2'b11;
    reg       b_miso = 1'b0;

    wire [CNT_W-1:0] ob_frames, ob_bits, ob_err, ob_xbits, ob_stale, ob_err_first,
                     ob_overlap, ob_half;
    wire [2:0]       dg_code;

    spi_miso_diag #(.DW(DW), .NB(NB), .CNT_W(CNT_W)) dut (
        .clk(clk), .rst_n(rst_n),
        .sclk(b_sclk), .cs_n(b_cs_n), .miso(b_miso),
        .cpol(cpol), .cpha(cpha), .word_exp(word_exp), .clr(clr),
        .ob_frames(ob_frames), .ob_bits(ob_bits), .ob_err(ob_err), .ob_xbits(ob_xbits),
        .ob_stale(ob_stale), .ob_err_first(ob_err_first), .ob_overlap(ob_overlap),
        .ob_half(ob_half), .dg_code(dg_code)
    );

    integer errors = 0;

    function [8*12:1] dname(input [2:0] c);
        begin
            case (c)
                D_CLEAN:      dname = "CLEAN       ";
                D_CONTENTION: dname = "CONTENTION  ";
                D_STALE:      dname = "STALE       ";
                D_ENABLE:     dname = "ENABLE      ";
                default:      dname = "OTHER       ";
            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

    // ---- the waveform tables, built before anything is driven ----
    reg       w_sclk [0:MAXT-1];
    reg [1:0] w_cs   [0:MAXT-1];
    reg       w_miso [0:MAXT-1];
    integer   w_len;

    // Build one frame. Returns through the tables above.
    //
    //   half     the SCLK half-period in system-clock ticks -- the experiment's variable
    //   cond     which condition to inject
    //   w        the payload the slave intends to return
    task automatic build(input [DW-1:0] w, input integer half, input integer cond);
        integer t, j, i, d, vt, shift, le, te;
        reg     cur;
        begin
            w_len = LEAD + NB*2*half + LAG + 1 + GAP;
            shift = (cond == C_EARLY) ? 1 : 0;

            for (t = 0; t < MAXT; t = t + 1) begin
                w_sclk[t] = cpol;
                w_cs[t]   = 2'b11;
                w_miso[t] = 1'b0;
            end

            // SCLK and the select. The select falls at tick 0 and rises after the lag.
            for (t = 0; t < w_len; t = t + 1) begin
                if (t < LEAD + NB*2*half + LAG) w_cs[t] = 2'b10;   // only device 0 selected
                cur = cpol;
                for (i = 0; i < NB; i = i + 1) begin
                    le = LEAD + i*2*half;
                    te = le + half;
                    if (t >= le && t < te) cur = ~cpol;
                end
                w_sclk[t] = cur;
            end

            // MISO, one bit at a time, placed at the tick it actually becomes valid.
            //
            // Bit j is launched at the trailing edge of bit-time (j-1+shift) and becomes valid `d`
            // ticks later. `shift` of 1 makes the slave permanently one bit behind, which is the
            // STRUCTURAL fault -- and note that it does not reference `half` at all, which is precisely
            // why slowing the clock cannot cure it.
            //
            // Bit 0 with no shift is placed right after the select, because a correct slave has its
            // first bit on the pin before the first edge.
            for (j = 0; j < NB; j = j + 1) begin
                // The per-bit delay. Varying it across bits is what makes the MARGINAL condition look
                // like a real one: some bits make their deadline at a given period and others do not,
                // so the error count falls gradually rather than all at once.
                // Delays of 3, 4 and 5 ticks, cycling by bit index. The values are chosen so that at
                // the FASTEST rate every bit misses its deadline -- which is what makes the marginal
                // link indistinguishable from the structural fault there, and the indistinguishability
                // is the result the chapter is built on. At the middle rate some bits make it and
                // others do not; at the slowest every bit does. A bit is in time exactly when d <= half.
                d = (cond == C_MARG) ? (3 + (j % 3)) : 1;
                if ((j == 0) && (shift == 0)) begin
                    vt = 1;
                end else begin
                    te = LEAD + (j - 1 + shift)*2*half + half;
                    vt = te + d;
                end
                if (vt < 0) vt = 0;
                for (t = vt; t < MAXT; t = t + 1)
                    w_miso[t] = w[NB-1-j];
            end

            // CONTENTION. A second select overlaps bit-times 2 to 5, and while two devices drive the
            // pin its level is INDETERMINATE -- which is the honest value for a contested net and is
            // what makes this fault's signature a level rather than a value.
            if (cond == C_CONT) begin
                for (t = LEAD + 2*2*half; t < LEAD + 6*2*half; t = t + 1) begin
                    w_cs[t]   = 2'b00;
                    w_miso[t] = 1'bx;
                end
            end
        end
    endtask

    task automatic drive;
        integer t;
        begin
            for (t = 0; t < w_len; t = t + 1) begin
                b_sclk = w_sclk[t];
                b_cs_n = w_cs[t];
                b_miso = w_miso[t];
                @(negedge clk);
            end
            b_sclk = cpol; b_cs_n = 2'b11; b_miso = 1'b0;
            idle_n(2);
        end
    endtask

    // ---- one run: four frames at one clock rate under one condition ----
    // Sized for FOURTEEN runs, not twelve. The first version stopped at twelve -- the size of the
    // 4x3 sweep -- and the two extra payload runs at the end wrote past the end of every array. Icarus
    // returns X for an out-of-range read rather than faulting, so the conclusion printed `x errors`
    // instead of a number and the comparison that guards it evaluated to false, which `if` treats as
    // not-a-failure. A silent out-of-range read is exactly the shape of bug this module is about.
    integer r_err [0:15], r_x[0:15], r_stale[0:15], r_first[0:15], r_ovl[0:15], r_half[0:15];
    reg [2:0] r_code [0:15];
    integer r_bits [0:15];
    integer run_i, ci, hi, k;
    integer HALVES [0:2];

    task automatic run(input integer cond, input integer half, input [DW-1:0] w,
                       input [8*44:1] label);
        begin
            clr = 1'b1; @(negedge clk); clr = 1'b0; @(negedge clk);
            word_exp = w;
            for (k = 0; k < 4; k = k + 1) begin
                build(w, half, cond);
                drive;
            end
            @(negedge clk);
            r_err[run_i]   = ob_err;    r_x[run_i]     = ob_xbits;
            r_stale[run_i] = ob_stale;  r_first[run_i] = ob_err_first;
            r_ovl[run_i]   = ob_overlap; r_half[run_i] = ob_half;
            r_code[run_i]  = dg_code;   r_bits[run_i]  = ob_bits;

            $display("  %6d  %5d  %5d  %4d  %5d  %5d  %7d   %s  %0s",
                     half, ob_bits, ob_err, ob_xbits, ob_stale, ob_err_first, ob_overlap,
                     dname(dg_code), label);
            if (run_i > 15) begin
                $display("  FAIL: run index %0d exceeds the log arrays; every conclusion below would read X",
                         run_i);
                errors = errors + 1;
            end
            if (ob_frames != 16'd4) begin
                $display("  FAIL: run %0d saw %0d frames where 4 were driven", run_i, ob_frames);
                errors = errors + 1;
            end
            run_i = run_i + 1;
        end
    endtask

    localparam [7:0] PAT_AA = 8'hAA;   // every adjacent pair differs: a stale sample always shows
    localparam [7:0] PAT_F0 = 8'hF0;   // one transition in eight: a stale sample almost never shows

    integer mutations, p_aa, p_f0;

    initial begin
        HALVES[0] = 2; HALVES[1] = 3; HALVES[2] = 5;
        run_i = 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);

        $display("  half    bits  errs    x  stale  1st  overlap   verdict       condition");
        for (ci = 0; ci < 4; ci = ci + 1)
            for (hi = 0; hi < 3; hi = hi + 1)
                run(ci, HALVES[hi], {24'b0, PAT_AA},
                    (ci == C_CLEAN) ? "a correct link" :
                    (ci == C_CONT)  ? "CONTENTION -- a second select overlaps" :
                    (ci == C_MARG)  ? "MARGINAL -- per-bit delays of 3, 4, 5" :
                                      "STRUCTURAL -- the slave is one bit behind");

        // ================= 1. contention's signature is a level, not a value =================
        for (k = 0; k < 12; k = k + 1) begin
            if ((k >= 3) && (k < 6)) begin
                if (r_x[k] == 0) begin
                    $display("  FAIL: run %0d is a contention run and reported 0 indeterminate bits", k);
                    errors = errors + 1;
                end
                if (r_ovl[k] == 0) begin
                    $display("  FAIL: run %0d is a contention run and reported no select overlap", k);
                    errors = errors + 1;
                end
            end else begin
                if (r_x[k] != 0) begin
                    $display("  FAIL: run %0d is not a contention run and reported %0d indeterminate bits",
                             k, r_x[k]);
                    errors = errors + 1;
                end
                if (r_ovl[k] != 0) begin
                    $display("  FAIL: run %0d is not a contention run and reported %0d overlap cycles",
                             k, r_ovl[k]);
                    errors = errors + 1;
                end
            end
        end
        $display("");
        $display("    1. indeterminate sampled bits appeared in the three contention runs and in NO other run, and select overlap did the same. That is a signature no other fault here produces, and it is a LEVEL rather than a value -- a contested net is not carrying the wrong bit, it is carrying neither bit. It also means the evidence for contention is on the SELECT pins while the symptom is on MISO, which is why a capture of MISO alone can only ever say `something is wrong here`");

        // ================= 2. the pair that one VERDICT cannot separate =================
        // Runs 6,7,8 are MARGINAL at halves 2,3,5. Runs 9,10,11 are STRUCTURAL at the same halves.
        if (r_code[6] !== r_code[9]) begin
            $display("  FAIL: at the fastest rate the marginal and structural faults received different verdicts (%s against %s); the chapter's claim is that a verdict cannot separate them",
                     dname(r_code[6]), dname(r_code[9]));
            errors = errors + 1;
        end
        if (!(r_stale[6] == r_err[6] && r_stale[9] == r_err[9])) begin
            $display("  FAIL: one of the two faults produced errors that were not stale samples (%0d of %0d, %0d of %0d), so they do not share a signature",
                     r_stale[6], r_err[6], r_stale[9], r_err[9]);
            errors = errors + 1;
        end
        // THE WHOLE DIFFERENCE IS THE FIRST BIT OF EACH FRAME, and asserting that precisely is worth
        // more than asserting the counts are equal -- which they are not.
        if ((r_err[9] - r_err[6]) != (r_first[9] - r_first[6])) begin
            $display("  FAIL: the two faults' error counts differ by %0d and their first-bit errors by %0d; the claim is that the whole difference is the first bit",
                     r_err[9] - r_err[6], r_first[9] - r_first[6]);
            errors = errors + 1;
        end
        $display("    2. at half-period %0d the MARGINAL link and the STRUCTURALLY EARLY sampling instant both reported %s, and in both every single error was a stale sample -- %0d of %0d and %0d of %0d. The verdict cannot separate them and neither can the signature. Their error counts differ by %0d, and that difference is ENTIRELY the first bit of each frame: a structurally-early slave gets bit 0 wrong too, while a marginal one does not, because bit 0 has the whole select-to-first-edge lead to settle in and every later bit has only a half period. So the first bit is the one bit in a frame that carries no information about marginal timing -- and a four-in-thirty-two difference is not what anybody reads a verdict for",
                 r_half[6], dname(r_code[6]), r_stale[6], r_err[6], r_stale[9], r_err[9],
                 r_err[9] - r_err[6]);

        // ================= 3. the trend separates them, and the trend is an intervention =======
        if (!(r_err[6] > r_err[7] && r_err[7] > r_err[8] && r_err[8] == 0)) begin
            $display("  FAIL: the marginal fault's error count did not fall monotonically to zero (%0d, %0d, %0d)",
                     r_err[6], r_err[7], r_err[8]);
            errors = errors + 1;
        end
        if (!(r_err[9] == r_err[10] && r_err[10] == r_err[11] && r_err[9] > 0)) begin
            $display("  FAIL: the structural fault's error count was not constant across rates (%0d, %0d, %0d)",
                     r_err[9], r_err[10], r_err[11]);
            errors = errors + 1;
        end
        $display("    3. slowing the clock down separated them completely. The marginal link went %0d, %0d, %0d errors as the half-period went %0d, %0d, %0d -- monotonically to zero. The structural fault went %0d, %0d, %0d: it does not move, because the model of it never references the period at all and neither does the real fault. So the discriminator is not an observation, it is an INTERVENTION, and that is a different kind of debugging step from anything earlier in this module: there is no way to look harder at one capture and get this answer",
                 r_err[6], r_err[7], r_err[8], r_half[6], r_half[7], r_half[8],
                 r_err[9], r_err[10], r_err[11]);

        // ================= 4. the pattern is part of the instrument, the other way round ========
        run(C_MARG, HALVES[0], {24'b0, PAT_AA}, "MARGINAL with 0xaa -- 7 transitions");
        p_aa = r_err[run_i-1];
        run(C_MARG, HALVES[0], {24'b0, PAT_F0}, "MARGINAL with 0xf0 -- 1 transition");
        p_f0 = r_err[run_i-1];

        // And the two numbers must be REAL before they are compared. An X compared with `>` is false,
        // which `if` reads as a pass -- so the guard has to be for a metavalue first.
        if ((^p_aa === 1'bx) || (^p_f0 === 1'bx)) begin
            $display("  FAIL: a payload run's error count came back X, so result 4 measured nothing");
            errors = errors + 1;
        end
        if (!(p_aa > p_f0)) begin
            $display("  FAIL: the high-transition pattern did not reveal more errors than the low-transition one (%0d against %0d)",
                     p_aa, p_f0);
            errors = errors + 1;
        end
        $display("    4. the SAME physical fault at the SAME clock rate reported %0d errors with payload 0xaa and %0d with 0xf0. A stale sample returns the PREVIOUS bit, so it is only visible when adjacent bits differ -- 0xaa differs on all seven boundaries and 0xf0 on one. Chapter 18.3 measured 0xaa as one of the worst patterns available, because reversal and both rotations all map it to the same value; here it is the best one available. The pattern is part of the instrument in both chapters and the right choice is the OPPOSITE one, which is why `use a good test pattern` is not advice anybody can follow without knowing what they are looking for",
                 p_aa, p_f0);

        // ================= BENCH INTEGRITY =================
        if (r_code[0] !== D_CLEAN) mutations = mutations + 1;   // a clean run must be CLEAN
        if (r_err[8]  != 0)        mutations = mutations + 1;   // the slow marginal run must be clean
        if (mutations != 0) begin
            $display("  FAIL: %0d baseline expectation(s) did not hold, so nothing below is evidence", mutations);
            errors = errors + 1;
        end
        // And the checks must be able to fail: two deliberately wrong expectations.
        mutations = 0;
        if (r_code[3] !== D_CLEAN) mutations = mutations + 1;
        if (r_err[6]  != 0)        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 (r_bits[0] != 32) begin
            $display("  FAIL: a run sampled %0d bits where 4 frames of %0d bits were driven",
                     r_bits[0], NB);
            errors = errors + 1;
        end

        if (errors == 0) begin
            $display("");
            $display("    and the bench proved itself: the clean run was CLEAN and the slowest marginal run had zero errors, so the baselines hold; two deliberately wrong expectations mismatched; and every run's sampled-bit count matched the %0d bits driven",
                     r_bits[0]);
            $display("PASS: read corruption on MISO has one cause with a signature of its own and two that share one. CONTENTION produces sampled bits that are neither 0 nor 1, and it produced them in all three contention runs and in no other run -- but the evidence is select OVERLAP, which is on different pins from the symptom, so a capture of MISO alone can only say that something is wrong. The pair that matters is a MARGINAL link against a sampling instant that is structurally early: at half-period %0d both reported %s, and in both every error was a stale sample -- %0d of %0d against %0d of %0d -- so neither the verdict nor the signature separates them, and the faults are in different places needing different fixes. Slowing the clock separated them completely -- the marginal link went %0d, %0d, %0d errors across half-periods %0d, %0d, %0d while the structural fault stayed at %0d throughout -- which makes the discriminator an INTERVENTION rather than an observation, the first in this module that cannot be had by looking harder at a capture. And the instrument's own sensitivity is a property of the payload: the same fault reported %0d errors with 0xaa and %0d with 0xf0, because a stale sample is only visible when adjacent bits differ -- the exact reverse of Chapter 18.3, where 0xaa was among the worst patterns available. Finally, and this is a limit rather than a result: the timing half of a marginal link is modelled faithfully here and the PHYSICAL half is not modelled at all. Intermittency, temperature and voltage dependence, reflections, crosstalk and metastability are absent, so every error count above is deterministic where the thing it stands for is not",
                     r_half[6], dname(r_code[6]), r_stale[6], r_err[6], r_stale[9], r_err[9],
                     r_err[6], r_err[7], r_err[8], r_half[6], r_half[7], r_half[8],
                     r_err[9], p_aa, p_f0);
        end else begin
            $display("FAIL: %0d error(s)", errors);
        end
        $finish;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_miso_diag_tb.v — the same bench in Verilog-2001
// spi_miso_diag_tb.v
//
// FOUR CONDITIONS AT THREE CLOCK RATES, AND TWO FAULTS THAT ONE RATE CANNOT TELL APART.
//
// HOW THE TIMING IS MODELLED, stated before any result so that no reader has to guess how much of this
// is simulation and how much is physics.
//
// The bench BUILDS THE MISO WAVEFORM AS AN ARRAY OF PER-TICK VALUES before driving anything. Each bit
// becomes valid on the pin at
//
//     (the trailing edge that launched it) + d
//
// where `d` is a propagation delay in system-clock ticks. The master samples at the leading edge, one
// half-period later. So whether a bit is valid in time is a comparison between a FIXED delay and a
// half-period that the experiment varies -- which is exactly the arithmetic of a setup margin, and the
// reason slowing the clock fixes a marginal link.
//
// Building the waveform first rather than emitting it from a loop is deliberate: the timing model is
// then a table somebody can read and check, instead of behaviour that emerges from the order of
// statements in a driver.
//
// WHAT THIS DOES NOT MODEL, and the chapter repeats it where a reader cannot miss it: a real setup
// violation is intermittent, temperature- and voltage-dependent, and can leave a flip-flop metastable.
// This one is deterministic. The model is faithful about WHICH bits fail and why; it is silent about
// how often, and anything a reader concludes here about failure RATES is a property of the model.
//
// THE FOUR RESULTS.
//
//   1. CONTENTION HAS A SIGNATURE NO OTHER FAULT HAS: sampled bits that are neither 0 nor 1, together
//      with overlapping selects. The bench requires the indeterminate-bit count to be non-zero for
//      contention and ZERO for every other condition, so "indeterminate means contention" is measured
//      rather than assumed.
//
//   2. THE HEADLINE. A marginal link and a structurally-early sampling instant produce the SAME verdict
//      and the SAME signature -- every error a stale sample -- at any single rate. The bench requires
//      that, which is an assertion that a measurement FAILS to discriminate. Their error counts differ
//      by exactly the FIRST BIT of each frame, and the bench asserts that too: bit 0 has the whole
//      select-to-first-edge lead to settle in, so it is the one bit in a frame that says nothing about
//      marginal timing. Four counts in thirty-two is not what anybody reads a verdict for.
//
//   3. THE DISCRIMINATOR IS AN INTERVENTION. There is no observation at a single rate that separates
//      result 2's pair. The experiment has to change the system, which is a different kind of
//      diagnostic step from everything earlier in this module.
//
//   4. THE BEST DEBUG PATTERN IS THE OPPOSITE OF CHAPTER 18.3'S. A stale sample is only VISIBLE when
//      adjacent bits differ, so the same physical fault produces a large error count with 0xAA and a
//      small one with 0xF0. Chapter 18.3 measured 0xAA as one of the worst patterns for alignment
//      faults; here it is the best available. The pattern is part of the instrument in both chapters
//      and the right choice is the opposite one.

`timescale 1ns/1ps

module spi_miso_diag_tb;

    localparam DW    = 32;
    localparam NB    = 8;
    localparam CNT_W = 16;
    localparam LEAD  = 3;
    localparam LAG   = 2;
    localparam GAP   = 3;
    localparam MAXT  = 512;

    localparam [2:0] D_CLEAN = 3'd0, D_CONTENTION = 3'd1, D_STALE = 3'd2,
                     D_ENABLE = 3'd3, D_OTHER = 3'd4;

    // The four conditions.
    localparam C_CLEAN  = 0;
    localparam C_CONT   = 1;   // a second select overlaps -- two drivers on MISO
    localparam C_MARG   = 2;   // per-bit propagation delays of 3, 4 and 5 ticks
    localparam C_EARLY  = 3;   // the slave is structurally one bit behind, at every rate

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

    reg          cpol, cpha;
    reg [DW-1:0] word_exp;
    reg          clr;

    reg       b_sclk;
    reg [1:0] b_cs_n;
    reg       b_miso;

    wire [CNT_W-1:0] ob_frames, ob_bits, ob_err, ob_xbits, ob_stale, ob_err_first,
                     ob_overlap, ob_half;
    wire [2:0]       dg_code;

    spi_miso_diag #(.DW(DW), .NB(NB), .CNT_W(CNT_W)) dut (
        .clk(clk), .rst_n(rst_n),
        .sclk(b_sclk), .cs_n(b_cs_n), .miso(b_miso),
        .cpol(cpol), .cpha(cpha), .word_exp(word_exp), .clr(clr),
        .ob_frames(ob_frames), .ob_bits(ob_bits), .ob_err(ob_err), .ob_xbits(ob_xbits),
        .ob_stale(ob_stale), .ob_err_first(ob_err_first), .ob_overlap(ob_overlap),
        .ob_half(ob_half), .dg_code(dg_code)
    );

    integer errors;

        function [8*12:1] dname;
        input [2:0] c;
        begin
            case (c)
                D_CLEAN:      dname = "CLEAN       ";
                D_CONTENTION: dname = "CONTENTION  ";
                D_STALE:      dname = "STALE       ";
                D_ENABLE:     dname = "ENABLE      ";
                default:      dname = "OTHER       ";
            endcase
        end
    endfunction

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

    // ---- the waveform tables, built before anything is driven ----
    reg       w_sclk [0:MAXT-1];
    reg [1:0] w_cs   [0:MAXT-1];
    reg       w_miso [0:MAXT-1];
    integer   w_len;

    // Build one frame. Returns through the tables above.
    //
    //   half     the SCLK half-period in system-clock ticks -- the experiment's variable
    //   cond     which condition to inject
    //   w        the payload the slave intends to return
        task build;
        input [DW-1:0] w;
        input integer half;
        input integer cond;
        integer t, j, i, d, vt, shift, le, te;
        reg     cur;
        begin
            w_len = LEAD + NB*2*half + LAG + 1 + GAP;
            shift = (cond == C_EARLY) ? 1 : 0;

            for (t = 0; t < MAXT; t = t + 1) begin
                w_sclk[t] = cpol;
                w_cs[t]   = 2'b11;
                w_miso[t] = 1'b0;
            end

            // SCLK and the select. The select falls at tick 0 and rises after the lag.
            for (t = 0; t < w_len; t = t + 1) begin
                if (t < LEAD + NB*2*half + LAG) w_cs[t] = 2'b10;   // only device 0 selected
                cur = cpol;
                for (i = 0; i < NB; i = i + 1) begin
                    le = LEAD + i*2*half;
                    te = le + half;
                    if (t >= le && t < te) cur = ~cpol;
                end
                w_sclk[t] = cur;
            end

            // MISO, one bit at a time, placed at the tick it actually becomes valid.
            //
            // Bit j is launched at the trailing edge of bit-time (j-1+shift) and becomes valid `d`
            // ticks later. `shift` of 1 makes the slave permanently one bit behind, which is the
            // STRUCTURAL fault -- and note that it does not reference `half` at all, which is precisely
            // why slowing the clock cannot cure it.
            //
            // Bit 0 with no shift is placed right after the select, because a correct slave has its
            // first bit on the pin before the first edge.
            for (j = 0; j < NB; j = j + 1) begin
                // The per-bit delay. Varying it across bits is what makes the MARGINAL condition look
                // like a real one: some bits make their deadline at a given period and others do not,
                // so the error count falls gradually rather than all at once.
                // Delays of 3, 4 and 5 ticks, cycling by bit index. The values are chosen so that at
                // the FASTEST rate every bit misses its deadline -- which is what makes the marginal
                // link indistinguishable from the structural fault there, and the indistinguishability
                // is the result the chapter is built on. At the middle rate some bits make it and
                // others do not; at the slowest every bit does. A bit is in time exactly when d <= half.
                d = (cond == C_MARG) ? (3 + (j % 3)) : 1;
                if ((j == 0) && (shift == 0)) begin
                    vt = 1;
                end else begin
                    te = LEAD + (j - 1 + shift)*2*half + half;
                    vt = te + d;
                end
                if (vt < 0) vt = 0;
                for (t = vt; t < MAXT; t = t + 1)
                    w_miso[t] = w[NB-1-j];
            end

            // CONTENTION. A second select overlaps bit-times 2 to 5, and while two devices drive the
            // pin its level is INDETERMINATE -- which is the honest value for a contested net and is
            // what makes this fault's signature a level rather than a value.
            if (cond == C_CONT) begin
                for (t = LEAD + 2*2*half; t < LEAD + 6*2*half; t = t + 1) begin
                    w_cs[t]   = 2'b00;
                    w_miso[t] = 1'bx;
                end
            end
        end
    endtask

    task drive;
        integer t;
        begin
            for (t = 0; t < w_len; t = t + 1) begin
                b_sclk = w_sclk[t];
                b_cs_n = w_cs[t];
                b_miso = w_miso[t];
                @(negedge clk);
            end
            b_sclk = cpol; b_cs_n = 2'b11; b_miso = 1'b0;
            idle_n(2);
        end
    endtask

    // ---- one run: four frames at one clock rate under one condition ----
    // Sized for FOURTEEN runs, not twelve. The first version stopped at twelve -- the size of the
    // 4x3 sweep -- and the two extra payload runs at the end wrote past the end of every array. Icarus
    // returns X for an out-of-range read rather than faulting, so the conclusion printed `x errors`
    // instead of a number and the comparison that guards it evaluated to false, which `if` treats as
    // not-a-failure. A silent out-of-range read is exactly the shape of bug this module is about.
    integer r_err [0:15], r_x[0:15], r_stale[0:15], r_first[0:15], r_ovl[0:15], r_half[0:15];
    reg [2:0] r_code [0:15];
    integer r_bits [0:15];
    integer run_i, ci, hi, k;
    integer HALVES [0:2];

        task run;
        input integer cond;
        input integer half;
        input [DW-1:0] w;
        input [8*44:1] label;
        begin
            clr = 1'b1; @(negedge clk); clr = 1'b0; @(negedge clk);
            word_exp = w;
            for (k = 0; k < 4; k = k + 1) begin
                build(w, half, cond);
                drive;
            end
            @(negedge clk);
            r_err[run_i]   = ob_err;    r_x[run_i]     = ob_xbits;
            r_stale[run_i] = ob_stale;  r_first[run_i] = ob_err_first;
            r_ovl[run_i]   = ob_overlap; r_half[run_i] = ob_half;
            r_code[run_i]  = dg_code;   r_bits[run_i]  = ob_bits;

            $display("  %6d  %5d  %5d  %4d  %5d  %5d  %7d   %0s  %0s",
                     half, ob_bits, ob_err, ob_xbits, ob_stale, ob_err_first, ob_overlap,
                     dname(dg_code), label);
            if (run_i > 15) begin
                $display("  FAIL: run index %0d exceeds the log arrays; every conclusion below would read X",
                         run_i);
                errors = errors + 1;
            end
            if (ob_frames != 16'd4) begin
                $display("  FAIL: run %0d saw %0d frames where 4 were driven", run_i, ob_frames);
                errors = errors + 1;
            end
            run_i = run_i + 1;
        end
    endtask

    localparam [7:0] PAT_AA = 8'hAA;   // every adjacent pair differs: a stale sample always shows
    localparam [7:0] PAT_F0 = 8'hF0;   // one transition in eight: a stale sample almost never shows

    integer mutations, p_aa, p_f0;

    initial begin
        HALVES[0] = 2; HALVES[1] = 3; HALVES[2] = 5;
        run_i = 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);

        $display("  half    bits  errs    x  stale  1st  overlap   verdict       condition");
        for (ci = 0; ci < 4; ci = ci + 1)
            for (hi = 0; hi < 3; hi = hi + 1)
                run(ci, HALVES[hi], {24'b0, PAT_AA},
                    (ci == C_CLEAN) ? "a correct link" :
                    (ci == C_CONT)  ? "CONTENTION -- a second select overlaps" :
                    (ci == C_MARG)  ? "MARGINAL -- per-bit delays of 3, 4, 5" :
                                      "STRUCTURAL -- the slave is one bit behind");

        // ================= 1. contention's signature is a level, not a value =================
        for (k = 0; k < 12; k = k + 1) begin
            if ((k >= 3) && (k < 6)) begin
                if (r_x[k] == 0) begin
                    $display("  FAIL: run %0d is a contention run and reported 0 indeterminate bits", k);
                    errors = errors + 1;
                end
                if (r_ovl[k] == 0) begin
                    $display("  FAIL: run %0d is a contention run and reported no select overlap", k);
                    errors = errors + 1;
                end
            end else begin
                if (r_x[k] != 0) begin
                    $display("  FAIL: run %0d is not a contention run and reported %0d indeterminate bits",
                             k, r_x[k]);
                    errors = errors + 1;
                end
                if (r_ovl[k] != 0) begin
                    $display("  FAIL: run %0d is not a contention run and reported %0d overlap cycles",
                             k, r_ovl[k]);
                    errors = errors + 1;
                end
            end
        end
        $display("");
        $display("    1. indeterminate sampled bits appeared in the three contention runs and in NO other run, and select overlap did the same. That is a signature no other fault here produces, and it is a LEVEL rather than a value -- a contested net is not carrying the wrong bit, it is carrying neither bit. It also means the evidence for contention is on the SELECT pins while the symptom is on MISO, which is why a capture of MISO alone can only ever say `something is wrong here`");

        // ================= 2. the pair that one VERDICT cannot separate =================
        // Runs 6,7,8 are MARGINAL at halves 2,3,5. Runs 9,10,11 are STRUCTURAL at the same halves.
        if (r_code[6] !== r_code[9]) begin
            $display("  FAIL: at the fastest rate the marginal and structural faults received different verdicts (%0s against %0s); the chapter's claim is that a verdict cannot separate them",
                     dname(r_code[6]), dname(r_code[9]));
            errors = errors + 1;
        end
        if (!(r_stale[6] == r_err[6] && r_stale[9] == r_err[9])) begin
            $display("  FAIL: one of the two faults produced errors that were not stale samples (%0d of %0d, %0d of %0d), so they do not share a signature",
                     r_stale[6], r_err[6], r_stale[9], r_err[9]);
            errors = errors + 1;
        end
        // THE WHOLE DIFFERENCE IS THE FIRST BIT OF EACH FRAME, and asserting that precisely is worth
        // more than asserting the counts are equal -- which they are not.
        if ((r_err[9] - r_err[6]) != (r_first[9] - r_first[6])) begin
            $display("  FAIL: the two faults' error counts differ by %0d and their first-bit errors by %0d; the claim is that the whole difference is the first bit",
                     r_err[9] - r_err[6], r_first[9] - r_first[6]);
            errors = errors + 1;
        end
        $display("    2. at half-period %0d the MARGINAL link and the STRUCTURALLY EARLY sampling instant both reported %0s, and in both every single error was a stale sample -- %0d of %0d and %0d of %0d. The verdict cannot separate them and neither can the signature. Their error counts differ by %0d, and that difference is ENTIRELY the first bit of each frame: a structurally-early slave gets bit 0 wrong too, while a marginal one does not, because bit 0 has the whole select-to-first-edge lead to settle in and every later bit has only a half period. So the first bit is the one bit in a frame that carries no information about marginal timing -- and a four-in-thirty-two difference is not what anybody reads a verdict for",
                 r_half[6], dname(r_code[6]), r_stale[6], r_err[6], r_stale[9], r_err[9],
                 r_err[9] - r_err[6]);

        // ================= 3. the trend separates them, and the trend is an intervention =======
        if (!(r_err[6] > r_err[7] && r_err[7] > r_err[8] && r_err[8] == 0)) begin
            $display("  FAIL: the marginal fault's error count did not fall monotonically to zero (%0d, %0d, %0d)",
                     r_err[6], r_err[7], r_err[8]);
            errors = errors + 1;
        end
        if (!(r_err[9] == r_err[10] && r_err[10] == r_err[11] && r_err[9] > 0)) begin
            $display("  FAIL: the structural fault's error count was not constant across rates (%0d, %0d, %0d)",
                     r_err[9], r_err[10], r_err[11]);
            errors = errors + 1;
        end
        $display("    3. slowing the clock down separated them completely. The marginal link went %0d, %0d, %0d errors as the half-period went %0d, %0d, %0d -- monotonically to zero. The structural fault went %0d, %0d, %0d: it does not move, because the model of it never references the period at all and neither does the real fault. So the discriminator is not an observation, it is an INTERVENTION, and that is a different kind of debugging step from anything earlier in this module: there is no way to look harder at one capture and get this answer",
                 r_err[6], r_err[7], r_err[8], r_half[6], r_half[7], r_half[8],
                 r_err[9], r_err[10], r_err[11]);

        // ================= 4. the pattern is part of the instrument, the other way round ========
        run(C_MARG, HALVES[0], {24'b0, PAT_AA}, "MARGINAL with 0xaa -- 7 transitions");
        p_aa = r_err[run_i-1];
        run(C_MARG, HALVES[0], {24'b0, PAT_F0}, "MARGINAL with 0xf0 -- 1 transition");
        p_f0 = r_err[run_i-1];

        // And the two numbers must be REAL before they are compared. An X compared with `>` is false,
        // which `if` reads as a pass -- so the guard has to be for a metavalue first.
        if ((^p_aa === 1'bx) || (^p_f0 === 1'bx)) begin
            $display("  FAIL: a payload run's error count came back X, so result 4 measured nothing");
            errors = errors + 1;
        end
        if (!(p_aa > p_f0)) begin
            $display("  FAIL: the high-transition pattern did not reveal more errors than the low-transition one (%0d against %0d)",
                     p_aa, p_f0);
            errors = errors + 1;
        end
        $display("    4. the SAME physical fault at the SAME clock rate reported %0d errors with payload 0xaa and %0d with 0xf0. A stale sample returns the PREVIOUS bit, so it is only visible when adjacent bits differ -- 0xaa differs on all seven boundaries and 0xf0 on one. Chapter 18.3 measured 0xaa as one of the worst patterns available, because reversal and both rotations all map it to the same value; here it is the best one available. The pattern is part of the instrument in both chapters and the right choice is the OPPOSITE one, which is why `use a good test pattern` is not advice anybody can follow without knowing what they are looking for",
                 p_aa, p_f0);

        // ================= BENCH INTEGRITY =================
        if (r_code[0] !== D_CLEAN) mutations = mutations + 1;   // a clean run must be CLEAN
        if (r_err[8]  != 0)        mutations = mutations + 1;   // the slow marginal run must be clean
        if (mutations != 0) begin
            $display("  FAIL: %0d baseline expectation(s) did not hold, so nothing below is evidence", mutations);
            errors = errors + 1;
        end
        // And the checks must be able to fail: two deliberately wrong expectations.
        mutations = 0;
        if (r_code[3] !== D_CLEAN) mutations = mutations + 1;
        if (r_err[6]  != 0)        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 (r_bits[0] != 32) begin
            $display("  FAIL: a run sampled %0d bits where 4 frames of %0d bits were driven",
                     r_bits[0], NB);
            errors = errors + 1;
        end

        if (errors == 0) begin
            $display("");
            $display("    and the bench proved itself: the clean run was CLEAN and the slowest marginal run had zero errors, so the baselines hold; two deliberately wrong expectations mismatched; and every run's sampled-bit count matched the %0d bits driven",
                     r_bits[0]);
            $display("PASS: read corruption on MISO has one cause with a signature of its own and two that share one. CONTENTION produces sampled bits that are neither 0 nor 1, and it produced them in all three contention runs and in no other run -- but the evidence is select OVERLAP, which is on different pins from the symptom, so a capture of MISO alone can only say that something is wrong. The pair that matters is a MARGINAL link against a sampling instant that is structurally early: at half-period %0d both reported %0s, and in both every error was a stale sample -- %0d of %0d against %0d of %0d -- so neither the verdict nor the signature separates them, and the faults are in different places needing different fixes. Slowing the clock separated them completely -- the marginal link went %0d, %0d, %0d errors across half-periods %0d, %0d, %0d while the structural fault stayed at %0d throughout -- which makes the discriminator an INTERVENTION rather than an observation, the first in this module that cannot be had by looking harder at a capture. And the instrument's own sensitivity is a property of the payload: the same fault reported %0d errors with 0xaa and %0d with 0xf0, because a stale sample is only visible when adjacent bits differ -- the exact reverse of Chapter 18.3, where 0xaa was among the worst patterns available. Finally, and this is a limit rather than a result: the timing half of a marginal link is modelled faithfully here and the PHYSICAL half is not modelled at all. Intermittency, temperature and voltage dependence, reflections, crosstalk and metastability are absent, so every error count above is deterministic where the thing it stands for is not",
                     r_half[6], dname(r_code[6]), r_stale[6], r_err[6], r_stale[9], r_err[9],
                     r_err[6], r_err[7], r_err[8], r_half[6], r_half[7], r_half[8],
                     r_err[9], p_aa, p_f0);
        end else begin
            $display("FAIL: %0d error(s)", errors);
        end
        $finish;
    end


    initial begin
        cpol = 1'b0;
        cpha = 1'b0;
        clk = 1'b0;
        rst_n = 1'b1;
        word_exp = {DW{1'b0}};
        clr = 1'b0;
        b_sclk = 1'b0;
        b_cs_n = 2'b11;
        b_miso = 1'b0;
        errors = 0;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_miso_diag_tb.vhd — the same bench in VHDL
-- spi_miso_diag_tb.vhd
--
-- FOUR CONDITIONS AT THREE CLOCK RATES, AND TWO FAULTS THAT ONE VERDICT CANNOT TELL APART.
--
-- HOW THE TIMING IS MODELLED, stated before any result so that no reader has to guess how much of this
-- is simulation and how much is physics. Each bit becomes valid on MISO at
--
--     (the trailing edge that launched it) + d
--
-- where `d` is a propagation delay in system-clock ticks. The master samples at the leading edge, one
-- half-period later, so a bit is in time exactly when d <= half -- a comparison between a FIXED delay
-- and a half-period the experiment varies, which is the arithmetic of a setup margin and the reason
-- slowing the clock fixes a marginal link. The waveform is BUILT AS A TABLE before anything is driven,
-- so the timing model is something a reader can check rather than behaviour emerging from a loop.
--
-- WHAT THIS DOES NOT MODEL: a real setup violation is intermittent, temperature- and voltage-dependent,
-- and can leave a flip-flop metastable. This one is deterministic. The model is faithful about WHICH
-- bits fail and why; it is silent about how often.
--
-- WHERE THIS VERSION IS BETTER THAN THE OTHER TWO. `std_logic` is a RESOLVED type, so contention here is
-- created by CONNECTING A SECOND DRIVER and the 'X' on MISO is the language resolving two devices
-- driving at once. The Verilog benches assign 1'bx during a computed window -- a hand-placed value
-- standing in for a physical effect, which would still appear exactly where it was told to even if the
-- window were computed wrongly. Here it appears only where two drivers genuinely coincide.
--
-- IDENTIFIER REVIEW (VHDL IS CASE-INSENSITIVE). `LEAD_C`, `LAG_C`, `GAP_C`, `NB_C`, `MAXT_C`, `PAT_AA_C`
-- and `PAT_F0_C` all carry suffixes so nothing can shadow them in another case; the condition selectors
-- are an enumeration rather than integers, so an out-of-range condition is not expressible.

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

entity spi_miso_diag_tb is
end entity spi_miso_diag_tb;

architecture tb of spi_miso_diag_tb is

    constant LEAD_C : natural  := 3;
    constant LAG_C  : natural  := 2;
    constant GAP_C  : natural  := 3;
    constant NB_C   : positive := 8;
    constant CNT_W  : positive := 16;
    constant MAXT_C : natural  := 512;

    subtype byte_t is std_logic_vector(7 downto 0);
    constant PAT_AA_C : byte_t := x"AA";   -- every adjacent pair differs: a stale sample always shows
    constant PAT_F0_C : byte_t := x"F0";   -- one transition in eight: a stale sample almost never shows

    -- The four conditions, as a type rather than as integers.
    type cond_t is (C_CLEAN, C_CONT, C_MARG, C_EARLY);

    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 clr  : std_logic := '0';

    signal b_sclk : std_logic := '0';
    signal b_cs_n : std_logic_vector(1 downto 0) := "11";

    -- TWO DRIVERS ON ONE RESOLVED SIGNAL. `b_miso` is what the decoder sees; the primary slave drives
    -- `miso_a` and the intruder drives `miso_b`, and when both drive, resolution produces 'X'. That is
    -- the contention model, and it is the language doing the work rather than the bench asserting a
    -- result.
    signal miso_a : std_logic := '0';
    signal miso_b : std_logic := 'Z';
    signal b_miso : std_logic;

    signal ob_frames, ob_bits, ob_err, ob_xbits : natural;
    signal ob_stale, ob_err_first, ob_overlap, ob_half : natural;
    signal dg_code : miso_diag_t;

    type nat_arr  is array (natural range <>) of natural;
    type dg_arr   is array (natural range <>) of miso_diag_t;

    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 cond_text (c : cond_t) return string is
    begin
        case c is
            when C_CLEAN => return "a correct link";
            when C_CONT  => return "CONTENTION -- a second select overlaps";
            when C_MARG  => return "MARGINAL -- per-bit delays of 3, 4, 5";
            when others  => return "STRUCTURAL -- the slave is one bit behind";
        end case;
    end function cond_text;

begin

    b_miso <= miso_a;
    b_miso <= miso_b;

    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_miso_diag
        generic map (NB_C => NB_C, CNT_W => CNT_W)
        port map (
            clk => clk, rst_n => rst_n,
            sclk => b_sclk, cs_n => b_cs_n, miso => b_miso,
            cpol => cpol, cpha => cpha, word_exp => word_exp, clr => clr,
            ob_frames => ob_frames, ob_bits => ob_bits, ob_err => ob_err, ob_xbits => ob_xbits,
            ob_stale => ob_stale, ob_err_first => ob_err_first, ob_overlap => ob_overlap,
            ob_half => ob_half, dg_code => dg_code
        );

    stim : process is

        type tick_arr is array (0 to MAXT_C - 1) of std_logic;
        type cs_arr   is array (0 to MAXT_C - 1) of std_logic_vector(1 downto 0);

        variable w_sclk : tick_arr;
        variable w_cs   : cs_arr;
        variable w_a    : tick_arr;   -- the primary slave's MISO
        variable w_b    : tick_arr;   -- the intruder's MISO ('Z' except during contention)
        variable w_len  : natural;

        variable r_err, r_x, r_stale, r_first, r_ovl, r_half, r_bits : nat_arr(0 to 15);
        variable r_code : dg_arr(0 to 15);
        variable run_i, e, mutations, p_aa, p_f0 : natural := 0;
        variable ln : line;

        constant HALVES_C : nat_arr(0 to 2) := (2, 3, 5);

        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;

        -- Build one frame into the tables above.
        procedure build (w : byte_t; half : natural; cond : cond_t) is
            variable shift : natural;
            variable d, vt, le, te : integer;
            variable cur : std_logic;
        begin
            w_len := LEAD_C + NB_C*2*half + LAG_C + 1 + GAP_C;
            if cond = C_EARLY then shift := 1; else shift := 0; end if;

            for t in 0 to MAXT_C - 1 loop
                w_sclk(t) := cpol;
                w_cs(t)   := "11";
                w_a(t)    := '0';
                w_b(t)    := 'Z';
            end loop;

            for t in 0 to w_len - 1 loop
                if t < LEAD_C + NB_C*2*half + LAG_C then w_cs(t) := "10"; end if;
                cur := cpol;
                for i in 0 to NB_C - 1 loop
                    le := LEAD_C + i*2*half;
                    te := le + half;
                    if t >= le and t < te then cur := not cpol; end if;
                end loop;
                w_sclk(t) := cur;
            end loop;

            -- Bit j is launched at the trailing edge of bit-time (j-1+shift) and becomes valid `d` ticks
            -- later. `shift` of 1 makes the slave permanently one bit behind -- the STRUCTURAL fault --
            -- and note that it does not reference `half` at all, which is precisely why slowing the
            -- clock cannot cure it. Bit 0 with no shift sits on the pin right after the select, because
            -- a correct slave has its first bit valid before the first edge.
            for j in 0 to NB_C - 1 loop
                -- Delays of 3, 4 and 5, cycling by bit index. Chosen so that at the FASTEST rate every
                -- bit misses its deadline: a bit is in time exactly when d <= half.
                if cond = C_MARG then d := 3 + (j mod 3); else d := 1; end if;
                if j = 0 and shift = 0 then
                    vt := 1;
                else
                    te := LEAD_C + (j - 1 + shift)*2*half + half;
                    vt := te + d;
                end if;
                if vt < 0 then vt := 0; end if;
                for t in vt to MAXT_C - 1 loop
                    w_a(t) := w(NB_C - 1 - j);
                end loop;
            end loop;

            -- CONTENTION: a second select overlaps bit-times 2 to 5, and the intruder DRIVES the pin.
            -- No 'X' is written anywhere -- resolution of two drivers produces it.
            if cond = C_CONT then
                for t in LEAD_C + 2*2*half to LEAD_C + 6*2*half - 1 loop
                    w_cs(t) := "00";
                    w_b(t)  := not w_a(t);
                end loop;
            end if;
        end procedure build;

        procedure drive is
        begin
            for t in 0 to w_len - 1 loop
                b_sclk <= w_sclk(t);
                b_cs_n <= w_cs(t);
                miso_a <= w_a(t);
                miso_b <= w_b(t);
                wait until falling_edge(clk);
            end loop;
            b_sclk <= cpol; b_cs_n <= "11"; miso_a <= '0'; miso_b <= 'Z';
            idle_n(2);
        end procedure drive;

        -- `caption`, NOT `label`. `label` is a VHDL RESERVED WORD, and using it as a parameter name is
        -- a parse error whose message points at the interface list rather than at the word -- the same
        -- trap Chapter 17.1 hit and renamed away from.
        procedure do_run (cond : cond_t; half : natural; w : byte_t; caption : string) is
        begin
            clr <= '1'; wait until falling_edge(clk); clr <= '0'; wait until falling_edge(clk);
            word_exp <= x"000000" & w;
            wait until falling_edge(clk);
            for k in 0 to 3 loop
                build(w, half, cond);
                drive;
            end loop;
            wait until falling_edge(clk);
            r_err(run_i)   := ob_err;    r_x(run_i)     := ob_xbits;
            r_stale(run_i) := ob_stale;  r_first(run_i) := ob_err_first;
            r_ovl(run_i)   := ob_overlap; r_half(run_i) := ob_half;
            r_code(run_i)  := dg_code;   r_bits(run_i)  := ob_bits;

            write(ln, string'("  ") & i2s(half, 6) & string'("  ") & i2s(ob_bits, 5)
                      & string'("  ") & i2s(ob_err, 5) & string'("  ") & i2s(ob_xbits, 4)
                      & string'("  ") & i2s(ob_stale, 5) & string'("  ") & i2s(ob_err_first, 5)
                      & string'("  ") & i2s(ob_overlap, 7) & string'("   ") & diag_name(dg_code)
                      & string'("  ") & caption);
            writeline(output, ln);

            if ob_frames /= 4 then
                write(ln, string'("  FAIL: run ") & i2s(run_i, 1) & string'(" saw ")
                          & i2s(ob_frames, 1) & string'(" frames where 4 were driven"));
                writeline(output, ln); e := e + 1;
            end if;
            run_i := run_i + 1;
        end procedure do_run;

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

        write(ln, string'("  half    bits  errs    x  stale  1st  overlap   verdict       condition"));
        writeline(output, ln);
        for ci in cond_t'pos(C_CLEAN) to cond_t'pos(C_EARLY) loop
            for hi in 0 to 2 loop
                do_run(cond_t'val(ci), HALVES_C(hi), PAT_AA_C, cond_text(cond_t'val(ci)));
            end loop;
        end loop;

        -- ================= 1. contention's signature is a level, not a value =================
        for k in 0 to 11 loop
            if k >= 3 and k < 6 then
                if r_x(k) = 0 then
                    write(ln, string'("  FAIL: run ") & i2s(k, 1)
                              & string'(" is a contention run and reported 0 indeterminate bits"));
                    writeline(output, ln); e := e + 1;
                end if;
                if r_ovl(k) = 0 then
                    write(ln, string'("  FAIL: run ") & i2s(k, 1)
                              & string'(" is a contention run and reported no select overlap"));
                    writeline(output, ln); e := e + 1;
                end if;
            else
                if r_x(k) /= 0 then
                    write(ln, string'("  FAIL: run ") & i2s(k, 1)
                              & string'(" is not a contention run and reported ") & i2s(r_x(k), 1)
                              & string'(" indeterminate bits"));
                    writeline(output, ln); e := e + 1;
                end if;
                if r_ovl(k) /= 0 then
                    write(ln, string'("  FAIL: run ") & i2s(k, 1)
                              & string'(" is not a contention run and reported ") & i2s(r_ovl(k), 1)
                              & string'(" overlap cycles"));
                    writeline(output, ln); e := e + 1;
                end if;
            end if;
        end loop;
        write(ln, string'(""));
        writeline(output, ln);
        write(ln, string'("    1. indeterminate sampled bits appeared in the three contention runs and in NO other run, and select overlap did the same. That is a signature no other fault here produces, and it is a LEVEL rather than a value -- a contested net is not carrying the wrong bit, it is carrying neither bit. It also means the evidence for contention is on the SELECT pins while the symptom is on MISO, which is why a capture of MISO alone can only ever say `something is wrong here`"));
        writeline(output, ln);

        -- ================= 2. the pair that one VERDICT cannot separate =================
        if r_code(6) /= r_code(9) then
            write(ln, string'("  FAIL: at the fastest rate the marginal and structural faults received different verdicts (")
                      & diag_name(r_code(6)) & string'(" against ") & diag_name(r_code(9))
                      & string'("); the chapter's claim is that a verdict cannot separate them"));
            writeline(output, ln); e := e + 1;
        end if;
        if not (r_stale(6) = r_err(6) and r_stale(9) = r_err(9)) then
            write(ln, string'("  FAIL: one of the two faults produced errors that were not stale samples, so they do not share a signature"));
            writeline(output, ln); e := e + 1;
        end if;
        -- THE WHOLE DIFFERENCE IS THE FIRST BIT OF EACH FRAME, and asserting that precisely is worth
        -- more than asserting the counts are equal -- which they are not.
        if (r_err(9) - r_err(6)) /= (r_first(9) - r_first(6)) then
            write(ln, string'("  FAIL: the two faults' error counts differ by ")
                      & i2s(r_err(9) - r_err(6), 1) & string'(" and their first-bit errors by ")
                      & i2s(r_first(9) - r_first(6), 1)
                      & string'("; the claim is that the whole difference is the first bit"));
            writeline(output, ln); e := e + 1;
        end if;
        write(ln, string'("    2. at half-period ") & i2s(r_half(6), 1)
                  & string'(" the MARGINAL link and the STRUCTURALLY EARLY sampling instant both reported ")
                  & diag_name(r_code(6))
                  & string'(", and in both every single error was a stale sample -- ")
                  & i2s(r_stale(6), 1) & string'(" of ") & i2s(r_err(6), 1) & string'(" and ")
                  & i2s(r_stale(9), 1) & string'(" of ") & i2s(r_err(9), 1)
                  & string'(". The verdict cannot separate them and neither can the signature. Their error counts differ by ")
                  & i2s(r_err(9) - r_err(6), 1)
                  & string'(", and that difference is ENTIRELY the first bit of each frame: a structurally-early slave gets bit 0 wrong too, while a marginal one does not, because bit 0 has the whole select-to-first-edge lead to settle in and every later bit has only a half period. So the first bit is the one bit in a frame that carries no information about marginal timing -- and a four-in-thirty-two difference is not what anybody reads a verdict for"));
        writeline(output, ln);

        -- ================= 3. the trend separates them, and the trend is an intervention =======
        if not (r_err(6) > r_err(7) and r_err(7) > r_err(8) and r_err(8) = 0) then
            write(ln, string'("  FAIL: the marginal fault's error count did not fall monotonically to zero"));
            writeline(output, ln); e := e + 1;
        end if;
        if not (r_err(9) = r_err(10) and r_err(10) = r_err(11) and r_err(9) > 0) then
            write(ln, string'("  FAIL: the structural fault's error count was not constant across rates"));
            writeline(output, ln); e := e + 1;
        end if;
        write(ln, string'("    3. slowing the clock down separated them completely. The marginal link went ")
                  & i2s(r_err(6), 1) & string'(", ") & i2s(r_err(7), 1) & string'(", ")
                  & i2s(r_err(8), 1) & string'(" errors as the half-period went ")
                  & i2s(r_half(6), 1) & string'(", ") & i2s(r_half(7), 1) & string'(", ")
                  & i2s(r_half(8), 1) & string'(" -- monotonically to zero. The structural fault went ")
                  & i2s(r_err(9), 1) & string'(", ") & i2s(r_err(10), 1) & string'(", ")
                  & i2s(r_err(11), 1)
                  & string'(": it does not move, because the model of it never references the period at all and neither does the real fault. So the discriminator is not an observation, it is an INTERVENTION, and that is a different kind of debugging step from anything earlier in this module: there is no way to look harder at one capture and get this answer"));
        writeline(output, ln);

        -- ================= 4. the pattern is part of the instrument, the other way round ========
        do_run(C_MARG, HALVES_C(0), PAT_AA_C, "MARGINAL with 0xaa -- 7 transitions");
        p_aa := r_err(run_i - 1);
        do_run(C_MARG, HALVES_C(0), PAT_F0_C, "MARGINAL with 0xf0 -- 1 transition");
        p_f0 := r_err(run_i - 1);

        if not (p_aa > p_f0) then
            write(ln, string'("  FAIL: the high-transition pattern did not reveal more errors than the low-transition one (")
                      & i2s(p_aa, 1) & string'(" against ") & i2s(p_f0, 1) & string'(")"));
            writeline(output, ln); e := e + 1;
        end if;
        write(ln, string'("    4. the SAME physical fault at the SAME clock rate reported ") & i2s(p_aa, 1)
                  & string'(" errors with payload 0xaa and ") & i2s(p_f0, 1)
                  & string'(" with 0xf0. A stale sample returns the PREVIOUS bit, so it is only visible when adjacent bits differ -- 0xaa differs on all seven boundaries and 0xf0 on one. Chapter 18.3 measured 0xaa as one of the worst patterns available, because reversal and both rotations all map it to the same value; here it is the best one available. The pattern is part of the instrument in both chapters and the right choice is the OPPOSITE one, which is why `use a good test pattern` is not advice anybody can follow without knowing what they are looking for"));
        writeline(output, ln);

        -- ================= BENCH INTEGRITY =================
        mutations := 0;
        if r_code(0) /= D_CLEAN then mutations := mutations + 1; end if;
        if r_err(8)  /= 0        then mutations := mutations + 1; end if;
        if mutations /= 0 then
            write(ln, string'("  FAIL: ") & i2s(mutations, 1)
                      & string'(" baseline expectation(s) did not hold, so nothing above is evidence"));
            writeline(output, ln); e := e + 1;
        end if;
        mutations := 0;
        if r_code(3) /= D_CLEAN then mutations := mutations + 1; end if;
        if r_err(6)  /= 0       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 r_bits(0) /= 32 then
            write(ln, string'("  FAIL: a run sampled ") & i2s(r_bits(0), 1)
                      & string'(" bits where 4 frames of ") & i2s(NB_C, 1)
                      & string'(" bits were driven"));
            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: the clean run was CLEAN and the slowest marginal run had zero errors, so the baselines hold; two deliberately wrong expectations mismatched; and every run's sampled-bit count matched the ")
                      & i2s(r_bits(0), 1) & string'(" bits driven"));
            writeline(output, ln);
            write(ln, string'("PASS: read corruption on MISO has one cause with a signature of its own and two that share one. CONTENTION produces sampled bits that are neither 0 nor 1, and it produced them in all three contention runs and in no other run -- but the evidence is select OVERLAP, which is on different pins from the symptom, so a capture of MISO alone can only say that something is wrong. The pair that matters is a MARGINAL link against a sampling instant that is structurally early: at half-period ")
                      & i2s(r_half(6), 1) & string'(" both reported ") & diag_name(r_code(6))
                      & string'(", and in both every error was a stale sample -- ")
                      & i2s(r_stale(6), 1) & string'(" of ") & i2s(r_err(6), 1)
                      & string'(" against ") & i2s(r_stale(9), 1) & string'(" of ")
                      & i2s(r_err(9), 1)
                      & string'(" -- so neither the verdict nor the signature separates them, and the faults are in different places needing different fixes. Slowing the clock separated them completely -- the marginal link went ")
                      & i2s(r_err(6), 1) & string'(", ") & i2s(r_err(7), 1) & string'(", ")
                      & i2s(r_err(8), 1) & string'(" errors across half-periods ")
                      & i2s(r_half(6), 1) & string'(", ") & i2s(r_half(7), 1) & string'(", ")
                      & i2s(r_half(8), 1) & string'(" while the structural fault stayed at ")
                      & i2s(r_err(9), 1)
                      & string'(" throughout -- which makes the discriminator an INTERVENTION rather than an observation, the first in this module that cannot be had by looking harder at a capture. And the instrument's own sensitivity is a property of the payload: the same fault reported ")
                      & i2s(p_aa, 1) & string'(" errors with 0xaa and ") & i2s(p_f0, 1)
                      & string'(" with 0xf0, because a stale sample is only visible when adjacent bits differ -- the exact reverse of Chapter 18.3, where 0xaa was among the worst patterns available. Finally, and this is a limit rather than a result: the timing half of a marginal link is modelled faithfully here and the PHYSICAL half is not modelled at all. Intermittency, temperature and voltage dependence, reflections, crosstalk and metastability are absent, so every error count above is deterministic where the thing it stands for is not"));
            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 A Simulation Of Marginal Timing Is Worth

Not nothing, and much less than it looks.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ✓ it establishes WHICH bits fail and why -- the arithmetic of delay against
     half period, and the fact that bit 0 is exempt because of the lead
   ✓ it establishes that the marginal and structural faults share a verdict
   ✓ it establishes that the trend separates them

   ✗ it says nothing about failure RATES
   ✗ it says nothing about margin -- there is no picosecond in the model
   ✗ a passing run is not evidence that a real link has margin

Everything in the first group is an argument about structure, and structure is exactly what a deterministic model can settle. Everything in the second requires a physical model the simulation does not have. Reading a number from the first group as if it belonged to the second is the mistake, and it is easy to make because the numbers look alike.

11. Where This Verification Actually Belongs

The useful UVM answer here is mostly about what does not belong in an RTL environment.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   the STRUCTURAL fault        an RTL assertion: the master samples on the
                              edge the mode specifies. Fully checkable.
   the marginal INTERNAL path  static timing analysis. Not simulation.
   the marginal BOARD path     signal-integrity simulation and lab measurement.
   the failure RATE            lab, over temperature and voltage.

Putting a check for marginal timing into a UVM regression is a false comfort: it passes for a design that will fail on a board, and its passing tells you only that your delay constants were smaller than your half period.

12. What This Decoder Cannot Do

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ✗ separate a marginal link from a structurally-early sampling instant at any
     single clock rate (that is the chapter's premise, not an oversight)
   ✗ say anything about failure rate, margin, or temperature dependence
   ✗ distinguish contention between two slaves from a slave that fails to release
     MISO on deselect -- both show overlap and indeterminate bits
   ✗ detect contention that does not overlap a sampling edge
   ✗ tell a stale sample from a correct sample when adjacent bits are equal

The third is worth a sentence because the fix differs. Two slaves selected at once is a decode fault in the master or the board; a slave that holds MISO after deselect is a fault in that slave. Separating them needs a capture where only one select is asserted and MISO is still contested — which is a stimulus change, and therefore Chapter 18.4's kind of discriminator applied to this chapter's fault.

13. Why an FPGA Engineer Cares

Two of these have FPGA-specific causes worth knowing. Contention on MISO is often a select decoder that is combinational on an address that glitches — the decode passes through an illegal state for a cycle and two selects go low together. Registering the decode output fixes it and costs one flip-flop per select. The overlap counter finds it in one run.

For the marginal case, the honest procedure is the intervention: halve the SCLK rate and re-run. If the errors vanish, the fault is timing and the next questions are about trace length, load capacitance, and whether the slave's output-valid time at your rate is actually within its datasheet. If they do not vanish, stop looking at the board — the sampling instant is wrong in your logic, and that is a fix in RTL.

That single experiment is worth more than any amount of scope work, and it takes one register write.

14. Why an ASIC Engineer Cares

The structural fault is yours and it is provable absent before tape-out: a rate-invariance test plus an assertion that the sampling edge matches the configured mode. Doing that work has a payoff that is easy to undervalue — it means a stale-sample failure in the lab can be attributed to the physical path without argument, because the other candidate has already been excluded.

The marginal fault is not an RTL question at all. The internal path from pad to capture flop is static timing analysis, the external path is a signal-integrity problem, and the failure rate is a lab measurement over corners. What the RTL owes is a sampling instant the STA can be run against and an input timing constraint that matches what the datasheet will promise — which is Chapter 15's material, and this chapter is the reason it matters.

And contention is a top-level integration question: if two selects can ever be low together, in any state including reset and scan, MISO is contested and the pads are fighting. That is checkable formally on the decode logic and is worth checking that way, because a simulation only finds the overlaps a stimulus happens to produce.

15. Failure Signature — "It Only Works At 1 MHz"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   Symptom     reads are corrupt at 8 MHz and clean at 1 MHz
   Concluded   the slave cannot keep up; the datasheet's 10 MHz is optimistic
   Action      the driver is locked to 1 MHz and shipped
   Cost        a throughput requirement missed by 8x, permanently, and a
               workaround nobody revisits because it works
   Actual      a 30 cm ribbon cable with no ground return next to it; the
               slave's output-valid time was well within spec and the
               interconnect was not
   Found       two years later, when the cable was shortened for mechanical
               reasons and somebody noticed 8 MHz had started working

The clock-rate experiment was performed and it gave the right answer to the wrong question. Slowing down fixes it correctly identifies the fault as marginal timing — and the conclusion drawn was about the slave rather than about the path to it. The missing step is the one section 13 names: once the fault is known to be marginal, the next question is which part of the path has no margin, and the answer is rarely the part with a datasheet.

16. Common Misconceptions

MisconceptionWhat is actually true
Corrupt reads mean the slave is faultyContention, marginal timing, and a wrong sampling edge are all master-or-board faults
An RTL regression can verify timing marginIt has no model of the physics that decides; a pass is not evidence
A stale sample means a marginal linkIt equally means a sampling instant that is wrong by design
Contention is visible on MISOIts effect is; its cause is on the select pins
An X in simulation is a modelling artefactFor a contested net it is the physically honest value
"Slowing down fixes it" identifies the culpritIt identifies the fault class; the culprit is usually the interconnect
One good test pattern serves all debugging0xAA is the best pattern here and among the worst in 18.3
The first bit is as good as any other for timingIt has the whole lead to settle in, so it is the one bit exempt

17. Reason It Through

18. Understanding Check

19. Summary

Read corruption on MISO has one cause with a signature of its own and two that share one. Contention produces sampled bits that are neither 0 nor 1 — a level rather than a value, because a contested net is not carrying the wrong bit but neither bit — and it produced them in all three contention runs and in no other run. Its evidence, though, is select overlap, which is on different pins from the symptom, so a capture of MISO alone can only say that something is wrong.

The pair that matters is a marginal link against a sampling instant that is structurally early. At the fastest rate both reported the same verdict with every error a stale sample, 28 of 28 against 32 of 32, so neither the verdict nor the signature separates them — and the faults are in different places needing different fixes. Their counts differ by four in thirty-two, entirely the first bit of each frame, because bit 0 has the whole lead to settle in and is therefore the one bit exempt from marginal timing.

Slowing the clock separated them completely: the marginal link went 28, 20, 0 errors across half-periods 2, 3, 5, while the structural fault stayed at 32 throughout. That makes the discriminator an intervention rather than an observation — the first in this module that cannot be had by looking harder at a capture. And the instrument's own sensitivity is a property of the payload: the same fault reported 28 errors with 0xAA and 4 with 0xF0, the exact reverse of Chapter 18.3, where 0xAA was among the worst patterns available.

Finally, and this is a limit rather than a result: the timing half of a marginal link is modelled faithfully here and the physical half is not modelled at all. Intermittency, temperature and voltage dependence, reflections, crosstalk and metastability are absent, so every error count above is deterministic where the thing it stands for is not. A passing RTL regression is not evidence that a link has margin. What RTL can prove is rate invariance — and proving it eliminates the structural half of the pair, so that a field failure can be attributed to the physical half without argument.

20. What Comes Next

Six chapters have each taken one fault family and found its discriminator. Chapter 18.7 asks the question that comes first in practice and last in this module: given a failure, is it in the RTL, in a clock-domain crossing, in a constraint, or in the testbench? Three orthogonal perturbations — clock rate, the bench's sampling instant, and the random seed — map those four causes to four distinct signatures, and the chapter closes the module by making the choice of which discriminator to reach for into a measurement of its own.

Continue learning