Skip to content
VLSI Mentor

SPI · Module 20

Constrained Random and Coverage Closure

Reviewing the random generator before trusting it, where one trap makes 45 percent of draws illegal and another yields a perfectly uniform histogram over a period-four sequence, then closing 17 holes by classifying them.

Chapter 20.5 built checkers and fed them twelve hand-written transactions. This chapter replaces the table with a generator, and starts by reviewing the generator — because a constrained random test can be wrong because its constraints are wrong, and then every number it produces is confident and meaningless.

One of the two traps below yields a perfectly uniform histogram over a sequence whose period is four. A distribution review that plots histograms passes it.

1. The Generator Is Hand-Built, For Two Reasons

$urandom, randomize() with constraints, and covergroup are all SystemVerilog, and the simulator these examples run in rejects covergroup outright:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   cg.sv:3: syntax error
   cg.sv:3: error: Invalid module item.

That is the first reason. The second is more interesting: a hand-written linear congruential generator produces the same stream in SystemVerilog, Verilog-2001 and VHDL. That turns the three-language comparison from all three covered roughly the same ground into all three saw the identical 160 transactions and agreed on every bin. A randomised bench whose languages disagree cannot be compared at all — the coverage tables are of different tests.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   s = s * 1664525 + 1013904223     (mod 2^32)

Verilog truncates the product to 32 bits by context; VHDL's multiplication of two 32-bit unsigneds returns 64 bits, so the truncation is written out. Those agree because (a·b mod 2^32 + c) mod 2^32 = (a·b + c) mod 2^32, and the evidence that they agree in practice is that all three transcripts — every coverage count and both trap measurements — come out identical.

2. Trap One: Signed Modulo

value % 13 is the obvious way to get a width in the range 4 to 16. If value is signed — and $random is signed, which is what makes this a real bug rather than a curiosity — the remainder takes the sign of the dividend, so the result ranges −12 to +12.

Measured over 2000 draws:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    signed  4 + (s % 13) : min   -8 max   16 illegal  891 of 2000
    unsigned 4 + (hi % 13) : min    4 max   16 illegal    0 of 2000

891 of 2000 draws — 44.6% — are outside the legal range, and the constraint reads as though it says 4 to 16. Those draws do not vanish: they become requests the controller refuses, so a suite built on this generator spends nearly half its cycles exercising REQ-ERR-001 and believing it is exercising the width range. The coverage report would show the width bins thinly populated and the explanation would look like bad luck.

3. Trap Two: A Uniform Histogram Is Not Randomness

The low bits of a power-of-two-modulus LCG have short periods. Drawing two bits from the bottom gives this:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    low  bits[1:0]  histogram  500  500  500  500
    high bits[17:16] histogram  500  511  489  500

The low-bit histogram is perfect — exactly 25% in each bin over 2000 draws. The high-bit one is slightly ragged, which is what a real random source looks like. A distribution review that stops at the histogram would prefer the low bits.

Now look at the sequences:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    low  bits[1:0]  sequence 3 2 1 0 3 2 1 0 3 2 1 0 3 2 1 0
    high bits[17:16] sequence 3 0 3 0 2 2 3 0 0 3 1 0 0 0 2 2

The low bits have a period of four. They are not random at all; they are a counter. Any dimension drawn from them cycles deterministically, so a cross between two such dimensions can only ever reach the combinations that the two periods happen to align on — and the per-dimension histograms remain flawless while the cross is systematically starved.

4. The Coverage Model

Plain arrays of hit counts. Every bin answers the question what plausible bug survives if this is never exercised? — and a bin that cannot answer it is not in the model.

CoverpointBinsThe bug it would let through
cp_mode4 — one per CPOL/CPHA paira mode-decode fault in an unvisited mode
cp_order2 — MSB, LSBthe alignment path is separate per order
cp_width4 — exactly 4, 5–8, 9–12, 13–16boundary shifts and the 16-bit mask
cp_div3 — 0, 1–7, 8 and abovecfg_div = 0 is the one-cycle half-period edge
cp_dev4 — one per decoder outputa decoder that drops its last output
CrossBinsWhy this cross and not another
x_mode_order8mode and bit order are handled by different logic that meets in the datapath
x_mode_width16the alignment shift depends on width; the launch edge depends on mode
x_width_order8the reversal is width-dependent, and only in one order

Three crosses, 32 bins, and no full Cartesian product — mode × order × width × div × dev would be 1536 bins whose occupancy would say nothing about any specific fault.

Two bins are excluded from the denominator, each with a reason, and each asserted to stay at zero:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   illegal   a width below 4 that produced a frame     REQ-ERR-001 forbids it
   unreach   two chip selects low at once              one index, one decoder
A loop: run the regression, measure coverage, ask whether holes remain. If none, closure is reached. If holes remain, classify each one, write a targeted sequence for it, and run again.yesrerunnoRun regressionMeasure coverageCoverage holes?Write targeted sequenceCoverage closed
Figure 1 — the closure loop, and the step that gets skipped is the one in the middle. Measuring coverage and re-running is easy; deciding WHY a bin is empty is what determines whether the next run is useful. A hole caused by a constraint that cannot reach it is not fixed by more cycles, and a hole that is genuinely unreachable is not a hole at all.

5. Phase A — A Deliberately Narrow Constraint Set

40 transactions with cfg_width drawn from 4 to 8 and cfg_div from 1 to 3.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  C2 phase A: 40 transactions, width 4..8, div 1..3
    scored 40 transactions, 0 failures, 17 coverage holes
  C3 coverage after phase A
    cp_mode         4/ 4   holes  0
    cp_order        2/ 2   holes  0
    cp_width        2/ 4   holes  2
    cp_div          1/ 3   holes  2
    cp_dev          4/ 4   holes  0
    x_mode_order    8/ 8   holes  0
    x_mode_width    7/16   holes  9
    x_width_order   4/ 8   holes  4

Zero functional failures and 17 holes. The single-dimension coverpoints look healthy — mode, order and device are all complete — and the damage is concentrated in the crosses, which is the normal shape of a narrow constraint set.

6. Classification, Which Is The Step That Matters

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  C4 hole classification
    CONSTRAINT BUG  width range 4..8 cannot reach bins 9-12, 13-16
    CONSTRAINT BUG  div range 1..3 cannot reach bin 0 or bin 8+
    DEPENDENT       13 cross holes follow from the 2 above, not from
                    separate stimulus gaps -- fixing the ranges fixes them

Seventeen holes, two root causes. The thirteen cross holes are not independent gaps; they are the arithmetic consequence of two unreachable ranges, and every one of them closes when the ranges widen.

That distinction decides what to do next. Thirteen separate targeted sequences would be thirteen pieces of throwaway code that a single constraint change makes redundant. The classification is what turns a coverage report into a work list.

ClassMeaningCorrect action
constraint bugthe generator cannot produce the valuewiden or fix the constraint
dependentfollows from another holefix the cause, re-measure
stimulus gapreachable, but the scenario is never assembledwrite a targeted sequence
monitor bugit happened and was not sampledfix the sampling, not the stimulus
coverage-model bugthe bin is mis-specifiedfix the bin
unreachablethe design makes it impossibleexclude, with the reason recorded

7. Phase B, And The Hole Random Does Not Close

Widen the width to 4–16 and run 45 more. The divider is deliberately left at 0–7, so bin 8-and-above stays out of reach.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  C5 phase B: 45 transactions, width 4..16, div 0..7
    widened random leaves 2 holes
  C6 targeted closure
    2 targeted transactions added
  C7 final coverage
    cp_mode         4/ 4
    cp_order        2/ 2
    cp_width        4/ 4
    cp_div          3/ 3
    cp_dev          4/ 4
    x_mode_order    8/ 8
    x_mode_width  16/16
    x_width_order   8/ 8
    remaining holes 0

That shape is the normal end state of a random campaign: it closes the bulk cheaply and then stops improving, and the last bins have to be constructed. The targeted transactions are built for the specific bin — pick the mode and the width the empty cross bin names, and fire exactly that — which is the only way to close a hole deterministically rather than by running more cycles and hoping.

8. The Two Excluded Bins

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    excluded, not chased:
      illegal   width < 4 accepted   hits 0  (REQ-ERR-001 forbids it)
      unreach   two selects low      hits 0  (one index, one decoder)
      the illegal bin was offered a width of 3 and stayed 0

The last line is the part that makes the exclusions worth anything. A counter that stays at zero is indistinguishable from a counter that is not wired to anything — so the bench offers an illegal width of 3 at the end of the run and confirms the controller refuses it and the counter stays zero. Without that, both excluded bins would be two integers that were never going to change.

The unreachable one is excluded rather than removed, and the difference is documentation. Deleting it loses the record that somebody considered two-selects-low, established that the architecture makes it impossible, and decided not to chase it. Keeping it as an excluded bin with a stated reason means the next person to add a broadcast mode finds the note.

9. Code Coverage, Which Measures Something Else Entirely

nvc collects statement and branch coverage, so this is a measured result rather than a methodological aside. On the VHDL controller under the directed suite alone:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   statement    total  12   hit  11   UNHIT   1
   branch       total   4   hit   3   UNHIT   1

— that from a deliberately tiny probe design, used here to show the tool reports holes and not only hits, which is the only property that makes a coverage tool useful.

The distinction worth being precise about:

Functional coverageCode coverage
Askswas this scenario exercised?was this line executed?
Written bya person, from the specificationthe tool, from the source
100% meansevery scenario I thought of happenedevery line ran at least once
Blind toa scenario nobody modelleda scenario that shares lines with another
Cannot detecta missing requirementa missing requirement

Neither is verification. High code coverage does not prove protocol completeness: this controller's mode decoder is three lines, and a suite that only ever ran mode 0 would execute all three of them and report 100% statement coverage on the part of the design most likely to be wrong. High functional coverage does not prove structural exploration: the functional model has no bin for the default arm of the state machine, so a suite could close every functional bin without ever entering it.

And neither replaces the assertions, the scoreboard, or the review. A single percentage is not a verification status, and a report that leads with one is hiding what it did not measure.

10. The Constrained Random Bench

Azvya Education Pvt. Ltd.VLSI Mentor
spi_capstone_crv_tb.sv — the generator, its two traps measured, the coverage model, and the closure loop
// spi_capstone_crv_tb.sv
//
// Chapter 20.6 -- constrained random stimulus, and what closing coverage actually is.
//
// WHY THIS BENCH BUILDS ITS OWN RANDOM GENERATOR
//
//   Two reasons, and the second is the interesting one.
//
//   (1) `$urandom`, `randomize()` with constraints, and `covergroup` are all
//       SystemVerilog, and the simulator these examples run in rejects covergroup
//       outright:
//
//           cg.sv:3: syntax error
//           cg.sv:3: error: Invalid module item.
//
//   (2) A hand-written linear congruential generator produces the SAME STREAM in
//       SystemVerilog, Verilog-2001 and VHDL. That turns the three-language comparison
//       from "all three covered roughly the same ground" into "all three saw the
//       identical 160 transactions and agreed on every bin". A randomised bench whose
//       languages disagree cannot be compared at all.
//
//   The generator is deliberately the textbook one:
//
//       s = s * 1664525 + 1013904223   (mod 2^32)
//
//   and it carries two traps this chapter measures rather than warns about.
//
// TRAP 1 -- SIGNED MODULO
//
//   `value % 13` is the obvious way to get a width. If `value` is SIGNED -- and
//   `$random` is signed, which is what makes this a real bug rather than a curiosity --
//   the result ranges -12..+12, so `4 + (value % 13)` produces widths from -8 to 16.
//   Every negative one is an illegal configuration the generator was never meant to
//   produce, and the constraint LOOKS like it says 4..16.
//
// TRAP 2 -- A UNIFORM HISTOGRAM IS NOT EVIDENCE OF RANDOMNESS
//
//   The low bits of this LCG have period 4. Drawing 2 bits from the bottom gives a
//   PERFECTLY uniform histogram -- exactly 25% each over any multiple of four draws --
//   and a completely deterministic repeating sequence. A distribution review that only
//   plots histograms passes it. Printing the sequence fails it immediately.
//
// WHAT COVERAGE CLOSURE MEANS HERE
//
//   Phase A runs a deliberately NARROW constraint set and reports the holes. Phase B
//   classifies every hole, fixes the constraints that caused them, re-runs, and closes
//   whatever the widened random stimulus still misses with TARGETED transactions
//   constructed for the specific bin.
//
//   Two bins are excluded from the denominator rather than chased, each with a reason,
//   and both are asserted to stay at zero. "100% coverage" with an unreachable bin
//   quietly deleted is not closure, it is arithmetic.

`timescale 1ns/1ps

module spi_capstone_crv_tb;

    reg         clk, rst_n;
    reg         cfg_cpol, cfg_cpha, cfg_lsb;
    reg  [4:0]  cfg_width;
    reg  [7:0]  cfg_div;
    reg  [1:0]  cfg_dev;
    reg  [3:0]  cfg_lead, cfg_lag, cfg_idle;
    reg         start, abort;
    reg  [15:0] tx_data;
    wire        busy, done, cfg_err;
    wire [15:0] rx_data;
    wire [4:0]  bits_done;
    wire        sclk, mosi;
    wire [3:0]  cs_n;
    wire        miso;

    integer n_chk, n_err, n_neg;

    spi_capstone_ctrl #(.DATA_W(16), .MIN_WIDTH(4), .NDEV(4)) dut (
        .clk(clk), .rst_n(rst_n),
        .cfg_cpol(cfg_cpol), .cfg_cpha(cfg_cpha), .cfg_lsb_first(cfg_lsb),
        .cfg_width(cfg_width), .cfg_div(cfg_div), .cfg_dev(cfg_dev),
        .cfg_lead(cfg_lead), .cfg_lag(cfg_lag), .cfg_idle(cfg_idle),
        .start(start), .tx_data(tx_data), .abort(abort),
        .busy(busy), .done(done), .cfg_err(cfg_err),
        .rx_data(rx_data), .bits_done(bits_done),
        .sclk(sclk), .mosi(mosi), .cs_n(cs_n), .miso(miso)
    );

    always #5 clk = ~clk;

    // ---------------- oracle (same arithmetic as 20.5) -------------------------
    function [15:0] mask;
        input [4:0] w;
        reg [16:0] one;
        begin one = 17'd1; mask = ((one << w) - 17'd1); end
    endfunction

    function [15:0] revw;
        input [15:0] v; input [4:0] w;
        integer b;
        begin
            revw = 16'd0;
            for (b = 0; b < 16; b = b + 1) if (b < w) revw[w-1-b] = v[b];
        end
    endfunction

    function [15:0] ref_rx;
        input [15:0] sw; input [4:0] w; input lsb;
        begin ref_rx = lsb ? revw(sw & mask(w), w) : (sw & mask(w)); end
    endfunction

    function [15:0] ref_slave_rx;
        input [15:0] tx; input [4:0] w; input lsb;
        begin ref_slave_rx = lsb ? revw(tx & mask(w), w) : (tx & mask(w)); end
    endfunction

    // ---------------- the generator -------------------------------------------
    reg [31:0] lcg;

    task step_lcg;
        begin
            lcg = lcg * 32'd1664525 + 32'd1013904223;
        end
    endtask

    // Draw from the HIGH half. Bits [30:16] are used, never the bottom ones -- see
    // trap 2 and the measurement in group C1.
    function [14:0] draw;
        input [31:0] s;
        begin draw = s[30:16]; end
    endfunction

    // ---------------- pin-level device model ----------------------------------
    reg        slv_cpol, slv_cpha;
    reg [15:0] slv_word, slv_sr, slv_rx;
    reg [4:0]  slv_w, slv_idx, slv_nrx;
    reg        slv_miso, lead_s;
    wire       cs_any = ~(&cs_n);
    assign     miso = slv_miso;

    always @(posedge cs_any) begin
        slv_sr  = slv_word << (16 - slv_w);
        slv_rx  = 16'd0;
        slv_nrx = 5'd0;
        if (!slv_cpha) begin
            slv_miso = slv_sr[15]; slv_sr = slv_sr << 1; slv_idx = 5'd1;
        end else begin
            slv_miso = 1'b0;      slv_idx = 5'd0;
        end
    end

    always @(sclk) begin
        if (cs_any === 1'b1) begin
            lead_s = (sclk !== slv_cpol);
            if (slv_cpha ? !lead_s : lead_s) begin
                if (slv_nrx < slv_w) begin
                    slv_rx = {slv_rx[14:0], mosi}; slv_nrx = slv_nrx + 5'd1;
                end
            end
            if (slv_cpha ? lead_s : !lead_s) begin
                if (slv_idx < slv_w) begin
                    slv_miso = slv_sr[15]; slv_sr = slv_sr << 1;
                    slv_idx  = slv_idx + 5'd1;
                end
            end
        end
    end

    // ---------------- coverage model ------------------------------------------
    // Plain arrays of hit counts. Every bin below answers a question of the form
    // "what plausible bug survives if this is never exercised?" -- a bin that cannot
    // answer it is not in the model.
    integer cp_mode  [0:3];    // CPOL/CPHA -- a mode-decode bug hides in an unvisited mode
    integer cp_order [0:1];    // MSB/LSB   -- the alignment path is separate per order
    integer cp_width [0:3];    // 4 / 5-8 / 9-12 / 13-16 -- boundary and mid widths
    integer cp_div   [0:2];    // 0 / 1-7 / 8+  -- div=0 is the one-cycle half-period edge
    integer cp_dev   [0:3];    // which decoder output
    integer x_mode_order [0:7];
    integer x_mode_width [0:15];
    integer x_width_order[0:7];

    // EXCLUDED from the denominator, each for a stated reason, each asserted to be 0.
    integer ex_illegal_accepted;  // a width < 4 that produced a frame: REQ-ERR-001 says no
    integer ex_two_selects;       // two selects low: unreachable, one index -> one decoder

    function integer wbin;
        input [4:0] w;
        begin
            if (w == 5'd4)      wbin = 0;
            else if (w <= 5'd8) wbin = 1;
            else if (w <= 5'd12) wbin = 2;
            else                wbin = 3;
        end
    endfunction

    function integer dbin;
        input [7:0] d;
        begin
            if (d == 8'd0)      dbin = 0;
            else if (d <= 8'd7) dbin = 1;
            else                dbin = 2;
        end
    endfunction

    task sample_cov;
        input cpol_i; input cpha_i; input lsb_i; input [4:0] w;
        input [7:0] d; input [1:0] dv;
        integer m, o, wb;
        begin
            m  = {cpol_i, cpha_i};
            o  = lsb_i ? 1 : 0;
            wb = wbin(w);
            cp_mode[m]        = cp_mode[m] + 1;
            cp_order[o]       = cp_order[o] + 1;
            cp_width[wb]      = cp_width[wb] + 1;
            cp_div[dbin(d)]   = cp_div[dbin(d)] + 1;
            cp_dev[dv]        = cp_dev[dv] + 1;
            x_mode_order[m*2 + o]   = x_mode_order[m*2 + o] + 1;
            x_mode_width[m*4 + wb]  = x_mode_width[m*4 + wb] + 1;
            x_width_order[wb*2 + o] = x_width_order[wb*2 + o] + 1;
        end
    endtask

    function integer holes_in;
        input integer which;   // 0..7 selects the coverpoint / cross
        integer i, h;
        begin
            h = 0;
            if (which == 0) for (i=0;i<4;i=i+1)  if (cp_mode[i]==0)       h=h+1;
            if (which == 1) for (i=0;i<2;i=i+1)  if (cp_order[i]==0)      h=h+1;
            if (which == 2) for (i=0;i<4;i=i+1)  if (cp_width[i]==0)      h=h+1;
            if (which == 3) for (i=0;i<3;i=i+1)  if (cp_div[i]==0)        h=h+1;
            if (which == 4) for (i=0;i<4;i=i+1)  if (cp_dev[i]==0)        h=h+1;
            if (which == 5) for (i=0;i<8;i=i+1)  if (x_mode_order[i]==0)  h=h+1;
            if (which == 6) for (i=0;i<16;i=i+1) if (x_mode_width[i]==0)  h=h+1;
            if (which == 7) for (i=0;i<8;i=i+1)  if (x_width_order[i]==0) h=h+1;
            holes_in = h;
        end
    endfunction

    // A TASK, not a function, and the reason is a Verilog-2001 rule that catches
    // everyone once: a FUNCTION MUST HAVE AT LEAST ONE INPUT. Written as
    // `function integer total_holes;` with no arguments it compiles under
    // SystemVerilog and fails under -g2001 with
    //
    //     error: Function total_holes has no ports.
    //     : Functions must have at least one input port.
    //
    // A task may have outputs only, so the total comes back through one.
    task total_holes;
        output integer h;
        integer i;
        begin
            h = 0;
            for (i = 0; i < 8; i = i + 1) h = h + holes_in(i);
        end
    endtask

    // ---------------- monitors -------------------------------------------------
    integer n_edge, n_frames, n_low;
    reg     sclk_d, cs_d;

    always @(posedge clk) begin
        if (!rst_n) begin
            n_edge <= 0; n_frames <= 0; sclk_d <= 1'b0; cs_d <= 1'b0;
        end else begin
            n_low = (cs_n[0]?0:1)+(cs_n[1]?0:1)+(cs_n[2]?0:1)+(cs_n[3]?0:1);
            if (n_low > 1) ex_two_selects = ex_two_selects + 1;
            if (cs_any && !cs_d) n_edge <= 0;
            if (!cs_any && cs_d) n_frames <= n_frames + 1;
                // `cs_d` as well as `cs_any`: an edge is only a FRAME edge if a device
                // was ALREADY selected last cycle. A transition in the very cycle the
                // select falls is SCLK reaching its new idle level, not a clocking
                // edge -- the controller parks SCLK and asserts CS together, so when
                // the previous idle level differed the two coincide.
                //
                // Measured cost of omitting `cs_d`: the first transaction after reset
                // with CPOL=1 counted 17 edges instead of 16 in VHDL and 16 in
                // SystemVerilog, because a one-cycle difference in reset-release
                // timing decided whether the re-park landed inside the window. The
                // received data was correct in both. With the gate the measurement no
                // longer depends on that phase at all.
            if (cs_any && cs_d && (sclk !== sclk_d)) n_edge <= n_edge + 1;
            cs_d <= cs_any; sclk_d <= sclk;
        end
    end

    // ---------------- stimulus -------------------------------------------------
    task set_cfg;
        input cpol_i; input cpha_i; input lsb_i; input [4:0] w;
        input [7:0] dv; input [1:0] dv_n;
        begin
            cfg_cpol=cpol_i; cfg_cpha=cpha_i; cfg_lsb=lsb_i;
            cfg_width=w; cfg_div=dv; cfg_dev=dv_n;
            cfg_lead=4'd2; cfg_lag=4'd2; cfg_idle=4'd1;
            slv_cpol=cpol_i; slv_cpha=cpha_i; slv_w=w;
        end
    endtask

    task fire;
        input [15:0] d;
        begin
            @(negedge clk); tx_data = d; start = 1'b1;
            @(negedge clk); start = 1'b0;
        end
    endtask

    task wait_idle;
        input integer maxc; output gotd;
        integer g; reg seen;
        begin
            g=0; seen=1'b0;
            while (g < maxc) begin
                @(negedge clk); g=g+1;
                if (done) seen=1'b1;
                if (!busy && seen) g=maxc;
                else if (!busy && g>4) g=maxc;
            end
            gotd = seen;
        end
    endtask

    // One transaction: build it, run it, score it, sample coverage.
    task do_txn;
        input cpol_i; input cpha_i; input lsb_i; input [4:0] w;
        input [7:0] dv; input [1:0] dv_n; input [15:0] txd; input [15:0] swd;
        reg gd;
        begin
            set_cfg(cpol_i, cpha_i, lsb_i, w, dv, dv_n);
            slv_word = swd & mask(w);
            fire(txd);
            wait_idle(40000, gd);
            n_chk = n_chk + 1;
            if (!gd) begin
                n_err = n_err + 1;
                $display("    FAIL no done for w=%0d div=%0d", w, dv);
            end
            n_chk = n_chk + 1;
            if (rx_data !== ref_rx(swd & mask(w), w, lsb_i)) begin
                n_err = n_err + 1;
                $display("    FAIL rx w=%0d mode=%0d lsb=%0d got %04h exp %04h",
                         w, {cpol_i,cpha_i}, lsb_i, rx_data,
                         ref_rx(swd & mask(w), w, lsb_i));
            end
            n_chk = n_chk + 1;
            if ((slv_rx & mask(w)) !== ref_slave_rx(txd, w, lsb_i)) begin
                n_err = n_err + 1;
                $display("    FAIL device rx w=%0d got %04h exp %04h",
                         w, slv_rx & mask(w), ref_slave_rx(txd, w, lsb_i));
            end
            n_chk = n_chk + 1;
            if (n_edge !== 2 * w) begin
                n_err = n_err + 1;
                $display("    FAIL edges w=%0d got %0d exp %0d", w, n_edge, 2*w);
            end
            sample_cov(cpol_i, cpha_i, lsb_i, w, dv, dv_n);
        end
    endtask

    integer i, j, k, m, o, wb;
    integer h_before, h_after, root_causes, targeted;
    integer lo_hist [0:3];
    integer hi_hist [0:3];
    integer smin, smax, sbad;
    integer v;
    reg gdx;

    initial begin
        clk=1'b0; rst_n=1'b0; start=1'b0; abort=1'b0; tx_data=16'd0;
        cfg_cpol=1'b0; cfg_cpha=1'b0; cfg_lsb=1'b0; cfg_width=5'd8;
        cfg_div=8'd1; cfg_dev=2'd0; cfg_lead=4'd2; cfg_lag=4'd2; cfg_idle=4'd1;
        slv_cpol=1'b0; slv_cpha=1'b0; slv_w=5'd8; slv_word=16'd0;
        slv_miso=1'b0; slv_sr=16'd0; slv_rx=16'd0; slv_idx=5'd0; slv_nrx=5'd0;
        n_chk=0; n_err=0; n_neg=0; n_edge=0; n_frames=0;
        sclk_d=1'b0; cs_d=1'b0;
        ex_illegal_accepted=0; ex_two_selects=0;
        for (i=0;i<4;i=i+1)  cp_mode[i]=0;
        for (i=0;i<2;i=i+1)  cp_order[i]=0;
        for (i=0;i<4;i=i+1)  cp_width[i]=0;
        for (i=0;i<3;i=i+1)  cp_div[i]=0;
        for (i=0;i<4;i=i+1)  cp_dev[i]=0;
        for (i=0;i<8;i=i+1)  x_mode_order[i]=0;
        for (i=0;i<16;i=i+1) x_mode_width[i]=0;
        for (i=0;i<8;i=i+1)  x_width_order[i]=0;
        for (i=0;i<4;i=i+1)  begin lo_hist[i]=0; hi_hist[i]=0; end

        $display("=== Chapter 20.6 -- constrained random and coverage closure ===");

        // -----------------------------------------------------------------
        // C1 -- review the generator BEFORE trusting anything it produces.
        // -----------------------------------------------------------------
        $display("  C1 generator review, 2000 draws");
        lcg = 32'h1234_5678;
        smin = 999; smax = -999; sbad = 0;
        for (i = 0; i < 2000; i = i + 1) begin
            step_lcg;
            // The trap: treat the draw as SIGNED, exactly as `$random` is.
            v = 4 + ($signed(lcg) % 13);
            if (v < smin) smin = v;
            if (v > smax) smax = v;
            if (v < 4 || v > 16) sbad = sbad + 1;
        end
        $display("    signed  4 + (s %% 13) : min %4d max %4d illegal %4d of 2000",
                 smin, smax, sbad);

        lcg = 32'h1234_5678;
        smin = 999; smax = -999; sbad = 0;
        for (i = 0; i < 2000; i = i + 1) begin
            step_lcg;
            v = 4 + (draw(lcg) % 13);
            if (v < smin) smin = v;
            if (v > smax) smax = v;
            if (v < 4 || v > 16) sbad = sbad + 1;
        end
        $display("    unsigned 4 + (hi %% 13) : min %4d max %4d illegal %4d of 2000",
                 smin, smax, sbad);

        // Trap 2: low bits vs high bits.
        lcg = 32'h1234_5678;
        for (i = 0; i < 2000; i = i + 1) begin
            step_lcg;
            lo_hist[lcg[1:0]]   = lo_hist[lcg[1:0]] + 1;
            hi_hist[lcg[17:16]] = hi_hist[lcg[17:16]] + 1;
        end
        $display("    low  bits[1:0]  histogram %4d %4d %4d %4d",
                 lo_hist[0], lo_hist[1], lo_hist[2], lo_hist[3]);
        $display("    high bits[17:16] histogram %4d %4d %4d %4d",
                 hi_hist[0], hi_hist[1], hi_hist[2], hi_hist[3]);
        lcg = 32'h1234_5678;
        $write("    low  bits[1:0]  sequence ");
        for (i = 0; i < 16; i = i + 1) begin step_lcg; $write("%0d ", lcg[1:0]); end
        $display("");
        lcg = 32'h1234_5678;
        $write("    high bits[17:16] sequence ");
        for (i = 0; i < 16; i = i + 1) begin step_lcg; $write("%0d ", lcg[17:16]); end
        $display("");

        // A uniform low-bit histogram must NOT be accepted as evidence: the sequence
        // has period 4. This is checked, not merely narrated.
        n_chk = n_chk + 1;
        if (lo_hist[0] == 500 && lo_hist[1] == 500 &&
            lo_hist[2] == 500 && lo_hist[3] == 500) begin
            n_neg = n_neg + 1;
            $display("    low bits are PERFECTLY uniform and have period 4 -- rejected");
        end else begin
            n_err = n_err + 1;
            $display("    FAIL expected a perfectly uniform low-bit histogram");
        end

        // Reset is RELEASED ON A NEGEDGE, for the same reason `start` is driven on one.
        // Releasing it on a posedge puts the assignment in the same region as every
        // clocked block that tests it, and the order is undefined: the monitor may see
        // the old value or the new one. Measured cost of getting this wrong -- the
        // monitor held its reset one cycle longer in VHDL than in SystemVerilog, so the
        // idle re-park of SCLK to CPOL=1 was counted as a frame edge in one language
        // and not the other, and the first transaction of the run reported 17 edges
        // instead of 16 in exactly one of the three.
        repeat (4) @(posedge clk);
        @(negedge clk); rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // -----------------------------------------------------------------
        // C2 -- PHASE A: 40 transactions under a deliberately NARROW constraint.
        // -----------------------------------------------------------------
        $display("  C2 phase A: 40 transactions, width 4..8, div 1..3");
        lcg = 32'hACE1_0F0F;
        for (i = 0; i < 40; i = i + 1) begin
            step_lcg; m  = draw(lcg) % 4;
            step_lcg; o  = draw(lcg) % 2;
            step_lcg; wb = 4 + (draw(lcg) % 5);          // 4..8 only
            step_lcg; k  = 1 + (draw(lcg) % 3);          // 1..3 only
            step_lcg; j  = draw(lcg) % 4;
            step_lcg; v  = draw(lcg);
            do_txn(m[1], m[0], o[0], wb[4:0], k[7:0], j[1:0],
                   {v[7:0], v[14:7]}, {v[3:0], v[14:3]});
        end
        total_holes(h_before);
        $display("    scored %0d transactions, %0d failures, %0d coverage holes",
                 40, n_err, h_before);

        // -----------------------------------------------------------------
        // C3 -- report the holes per coverpoint.
        // -----------------------------------------------------------------
        $display("  C3 coverage after phase A");
        $display("    cp_mode        %2d/%2d   holes %2d", 4-holes_in(0),  4, holes_in(0));
        $display("    cp_order       %2d/%2d   holes %2d", 2-holes_in(1),  2, holes_in(1));
        $display("    cp_width       %2d/%2d   holes %2d", 4-holes_in(2),  4, holes_in(2));
        $display("    cp_div         %2d/%2d   holes %2d", 3-holes_in(3),  3, holes_in(3));
        $display("    cp_dev         %2d/%2d   holes %2d", 4-holes_in(4),  4, holes_in(4));
        $display("    x_mode_order   %2d/%2d   holes %2d", 8-holes_in(5),  8, holes_in(5));
        $display("    x_mode_width   %2d/%2d   holes %2d", 16-holes_in(6),16, holes_in(6));
        $display("    x_width_order  %2d/%2d   holes %2d", 8-holes_in(7),  8, holes_in(7));

        // -----------------------------------------------------------------
        // C4 -- classify. This is the step that gets skipped, and it is the step
        // that decides whether the next run is useful.
        // -----------------------------------------------------------------
        $display("  C4 hole classification");
        root_causes = 0;
        if (cp_width[2] == 0 || cp_width[3] == 0) begin
            root_causes = root_causes + 1;
            $display("    CONSTRAINT BUG  width range 4..8 cannot reach bins 9-12, 13-16");
        end
        if (cp_div[0] == 0 || cp_div[2] == 0) begin
            root_causes = root_causes + 1;
            $display("    CONSTRAINT BUG  div range 1..3 cannot reach bin 0 or bin 8+");
        end
        $display("    DEPENDENT       %0d cross holes follow from the %0d above, not from",
                 holes_in(6) + holes_in(7), root_causes);
        $display("                    separate stimulus gaps -- fixing the ranges fixes them");
        n_chk = n_chk + 1;
        if (root_causes != 2) begin
            n_err = n_err + 1;
            $display("    FAIL expected 2 constraint root causes, found %0d", root_causes);
        end

        // -----------------------------------------------------------------
        // C5 -- PHASE B: widened constraints.
        // -----------------------------------------------------------------
        // Phase B widens WIDTH but deliberately leaves DIV at 0..7, so bin 8+ is
        // still unreachable by random draw. That is not an oversight in the example:
        // it is the normal end state of a random campaign. Random stimulus closes the
        // bulk cheaply and then stops improving, and the last few bins have to be
        // constructed. A closure loop whose targeted stage never runs has not been
        // shown to work -- and with 120 transactions and div 0..11 it did not run,
        // which is how this batch size was chosen.
        $display("  C5 phase B: 45 transactions, width 4..16, div 0..7");
        lcg = 32'h5EED_1111;
        for (i = 0; i < 45; i = i + 1) begin
            step_lcg; m  = draw(lcg) % 4;
            step_lcg; o  = draw(lcg) % 2;
            step_lcg; wb = 4 + (draw(lcg) % 13);         // 4..16
            step_lcg; k  = draw(lcg) % 8;                // 0..7 -- bin 8+ left open
            step_lcg; j  = draw(lcg) % 4;
            step_lcg; v  = draw(lcg);
            do_txn(m[1], m[0], o[0], wb[4:0], k[7:0], j[1:0],
                   {v[7:0], v[14:7]}, {v[3:0], v[14:3]});
        end
        total_holes(h_after);
        $display("    widened random leaves %0d holes", h_after);

        // -----------------------------------------------------------------
        // C6 -- TARGETED closure for whatever random did not reach. Constructed for
        // the specific bin, which is the only way to close a hole deterministically.
        // -----------------------------------------------------------------
        $display("  C6 targeted closure");
        targeted = 0;
        for (i = 0; i < 16; i = i + 1) begin
            if (x_mode_width[i] == 0) begin
                m  = i / 4;
                wb = i % 4;
                if      (wb == 0) k = 4;
                else if (wb == 1) k = 7;
                else if (wb == 2) k = 11;
                else              k = 15;
                do_txn(m[1], m[0], 1'b0, k[4:0], 8'd1, 2'd0, 16'h9D5A, 16'h3CB9);
                targeted = targeted + 1;
            end
        end
        for (i = 0; i < 8; i = i + 1) begin
            if (x_width_order[i] == 0) begin
                wb = i / 2;
                o  = i % 2;
                if      (wb == 0) k = 4;
                else if (wb == 1) k = 6;
                else if (wb == 2) k = 10;
                else              k = 14;
                do_txn(1'b0, 1'b0, o[0], k[4:0], 8'd1, 2'd0, 16'h9D5A, 16'h3CB9);
                targeted = targeted + 1;
            end
        end
        for (i = 0; i < 3; i = i + 1) begin
            if (cp_div[i] == 0) begin
                if      (i == 0) k = 0;
                else if (i == 1) k = 4;
                else             k = 9;
                do_txn(1'b0, 1'b0, 1'b0, 5'd8, k[7:0], 2'd0, 16'h9D5A, 16'h3CB9);
                targeted = targeted + 1;
            end
        end
        $display("    %0d targeted transactions added", targeted);

        // -----------------------------------------------------------------
        // C7 -- final coverage, and the two bins that are EXCLUDED with reasons.
        // -----------------------------------------------------------------
        $display("  C7 final coverage");
        $display("    cp_mode        %2d/%2d", 4-holes_in(0),  4);
        $display("    cp_order       %2d/%2d", 2-holes_in(1),  2);
        $display("    cp_width       %2d/%2d", 4-holes_in(2),  4);
        $display("    cp_div         %2d/%2d", 3-holes_in(3),  3);
        $display("    cp_dev         %2d/%2d", 4-holes_in(4),  4);
        $display("    x_mode_order   %2d/%2d", 8-holes_in(5),  8);
        $display("    x_mode_width  %2d/%2d", 16-holes_in(6), 16);
        $display("    x_width_order  %2d/%2d", 8-holes_in(7),  8);
        total_holes(h_after);
        $display("    remaining holes %0d", h_after);
        n_chk = n_chk + 1;
        if (h_after != 0) begin
            n_err = n_err + 1;
            $display("    FAIL %0d holes remain after targeted closure", h_after);
        end

        $display("    excluded, not chased:");
        $display("      illegal   width < 4 accepted   hits %0d  (REQ-ERR-001 forbids it)",
                 ex_illegal_accepted);
        $display("      unreach   two selects low      hits %0d  (one index, one decoder)",
                 ex_two_selects);
        n_chk = n_chk + 1;
        if (ex_illegal_accepted != 0 || ex_two_selects != 0) begin
            n_err = n_err + 1;
            $display("    FAIL an excluded bin was hit -- the exclusion was wrong");
        end

        // An illegal width is offered once, to prove the illegal bin's counter is
        // wired to something and is not simply a variable that stays at zero.
        set_cfg(1'b0, 1'b0, 1'b0, 5'd3, 8'd1, 2'd0);
        fire(16'h1234);
        if (busy) ex_illegal_accepted = ex_illegal_accepted + 1;
        n_chk = n_chk + 1;
        if (ex_illegal_accepted != 0) begin
            n_err = n_err + 1;
            $display("    FAIL an illegal width started a frame");
        end else begin
            n_neg = n_neg + 1;
            $display("      the illegal bin was offered a width of 3 and stayed 0");
        end

        $display("=== SUMMARY checks=%0d negatives=%0d failures=%0d : %0s ===",
                 n_chk, n_neg, n_err, (n_err == 0) ? "PASS" : "FAIL");
        $finish;
    end

    initial begin
        #40000000;
        $display("    FATAL global timeout");
        $display("=== SUMMARY checks=%0d negatives=%0d failures=%0d : FAIL ===",
                 n_chk, n_neg, n_err + 1);
        $finish;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_capstone_crv_tb.v — the same bench in Verilog-2001, where a zero-argument function is illegal
// spi_capstone_crv_tb.sv
//
// Chapter 20.6 -- constrained random stimulus, and what closing coverage actually is.
//
// WHY THIS BENCH BUILDS ITS OWN RANDOM GENERATOR
//
//   Two reasons, and the second is the interesting one.
//
//   (1) `$urandom`, `randomize()` with constraints, and `covergroup` are all
//       SystemVerilog, and the simulator these examples run in rejects covergroup
//       outright:
//
//           cg.sv:3: syntax error
//           cg.sv:3: error: Invalid module item.
//
//   (2) A hand-written linear congruential generator produces the SAME STREAM in
//       SystemVerilog, Verilog-2001 and VHDL. That turns the three-language comparison
//       from "all three covered roughly the same ground" into "all three saw the
//       identical 160 transactions and agreed on every bin". A randomised bench whose
//       languages disagree cannot be compared at all.
//
//   The generator is deliberately the textbook one:
//
//       s = s * 1664525 + 1013904223   (mod 2^32)
//
//   and it carries two traps this chapter measures rather than warns about.
//
// TRAP 1 -- SIGNED MODULO
//
//   `value % 13` is the obvious way to get a width. If `value` is SIGNED -- and
//   `$random` is signed, which is what makes this a real bug rather than a curiosity --
//   the result ranges -12..+12, so `4 + (value % 13)` produces widths from -8 to 16.
//   Every negative one is an illegal configuration the generator was never meant to
//   produce, and the constraint LOOKS like it says 4..16.
//
// TRAP 2 -- A UNIFORM HISTOGRAM IS NOT EVIDENCE OF RANDOMNESS
//
//   The low bits of this LCG have period 4. Drawing 2 bits from the bottom gives a
//   PERFECTLY uniform histogram -- exactly 25% each over any multiple of four draws --
//   and a completely deterministic repeating sequence. A distribution review that only
//   plots histograms passes it. Printing the sequence fails it immediately.
//
// WHAT COVERAGE CLOSURE MEANS HERE
//
//   Phase A runs a deliberately NARROW constraint set and reports the holes. Phase B
//   classifies every hole, fixes the constraints that caused them, re-runs, and closes
//   whatever the widened random stimulus still misses with TARGETED transactions
//   constructed for the specific bin.
//
//   Two bins are excluded from the denominator rather than chased, each with a reason,
//   and both are asserted to stay at zero. "100% coverage" with an unreachable bin
//   quietly deleted is not closure, it is arithmetic.

`timescale 1ns/1ps

module spi_capstone_crv_tb;

    reg         clk, rst_n;
    reg         cfg_cpol, cfg_cpha, cfg_lsb;
    reg  [4:0]  cfg_width;
    reg  [7:0]  cfg_div;
    reg  [1:0]  cfg_dev;
    reg  [3:0]  cfg_lead, cfg_lag, cfg_idle;
    reg         start, abort;
    reg  [15:0] tx_data;
    wire        busy, done, cfg_err;
    wire [15:0] rx_data;
    wire [4:0]  bits_done;
    wire        sclk, mosi;
    wire [3:0]  cs_n;
    wire        miso;

    integer n_chk, n_err, n_neg;

    spi_capstone_ctrl #(.DATA_W(16), .MIN_WIDTH(4), .NDEV(4)) dut (
        .clk(clk), .rst_n(rst_n),
        .cfg_cpol(cfg_cpol), .cfg_cpha(cfg_cpha), .cfg_lsb_first(cfg_lsb),
        .cfg_width(cfg_width), .cfg_div(cfg_div), .cfg_dev(cfg_dev),
        .cfg_lead(cfg_lead), .cfg_lag(cfg_lag), .cfg_idle(cfg_idle),
        .start(start), .tx_data(tx_data), .abort(abort),
        .busy(busy), .done(done), .cfg_err(cfg_err),
        .rx_data(rx_data), .bits_done(bits_done),
        .sclk(sclk), .mosi(mosi), .cs_n(cs_n), .miso(miso)
    );

    always #5 clk = ~clk;

    // ---------------- oracle (same arithmetic as 20.5) -------------------------
    function [15:0] mask;
        input [4:0] w;
        reg [16:0] one;
        begin one = 17'd1; mask = ((one << w) - 17'd1); end
    endfunction

    function [15:0] revw;
        input [15:0] v; input [4:0] w;
        integer b;
        begin
            revw = 16'd0;
            for (b = 0; b < 16; b = b + 1) if (b < w) revw[w-1-b] = v[b];
        end
    endfunction

    function [15:0] ref_rx;
        input [15:0] sw; input [4:0] w; input lsb;
        begin ref_rx = lsb ? revw(sw & mask(w), w) : (sw & mask(w)); end
    endfunction

    function [15:0] ref_slave_rx;
        input [15:0] tx; input [4:0] w; input lsb;
        begin ref_slave_rx = lsb ? revw(tx & mask(w), w) : (tx & mask(w)); end
    endfunction

    // ---------------- the generator -------------------------------------------
    reg [31:0] lcg;

    task step_lcg;
        begin
            lcg = lcg * 32'd1664525 + 32'd1013904223;
        end
    endtask

    // Draw from the HIGH half. Bits [30:16] are used, never the bottom ones -- see
    // trap 2 and the measurement in group C1.
    function [14:0] draw;
        input [31:0] s;
        begin draw = s[30:16]; end
    endfunction

    // ---------------- pin-level device model ----------------------------------
    reg        slv_cpol, slv_cpha;
    reg [15:0] slv_word, slv_sr, slv_rx;
    reg [4:0]  slv_w, slv_idx, slv_nrx;
    reg        slv_miso, lead_s;
    wire       cs_any = ~(&cs_n);
    assign     miso = slv_miso;

    always @(posedge cs_any) begin
        slv_sr  = slv_word << (16 - slv_w);
        slv_rx  = 16'd0;
        slv_nrx = 5'd0;
        if (!slv_cpha) begin
            slv_miso = slv_sr[15]; slv_sr = slv_sr << 1; slv_idx = 5'd1;
        end else begin
            slv_miso = 1'b0;      slv_idx = 5'd0;
        end
    end

    always @(sclk) begin
        if (cs_any === 1'b1) begin
            lead_s = (sclk !== slv_cpol);
            if (slv_cpha ? !lead_s : lead_s) begin
                if (slv_nrx < slv_w) begin
                    slv_rx = {slv_rx[14:0], mosi}; slv_nrx = slv_nrx + 5'd1;
                end
            end
            if (slv_cpha ? lead_s : !lead_s) begin
                if (slv_idx < slv_w) begin
                    slv_miso = slv_sr[15]; slv_sr = slv_sr << 1;
                    slv_idx  = slv_idx + 5'd1;
                end
            end
        end
    end

    // ---------------- coverage model ------------------------------------------
    // Plain arrays of hit counts. Every bin below answers a question of the form
    // "what plausible bug survives if this is never exercised?" -- a bin that cannot
    // answer it is not in the model.
    integer cp_mode  [0:3];    // CPOL/CPHA -- a mode-decode bug hides in an unvisited mode
    integer cp_order [0:1];    // MSB/LSB   -- the alignment path is separate per order
    integer cp_width [0:3];    // 4 / 5-8 / 9-12 / 13-16 -- boundary and mid widths
    integer cp_div   [0:2];    // 0 / 1-7 / 8+  -- div=0 is the one-cycle half-period edge
    integer cp_dev   [0:3];    // which decoder output
    integer x_mode_order [0:7];
    integer x_mode_width [0:15];
    integer x_width_order[0:7];

    // EXCLUDED from the denominator, each for a stated reason, each asserted to be 0.
    integer ex_illegal_accepted;  // a width < 4 that produced a frame: REQ-ERR-001 says no
    integer ex_two_selects;       // two selects low: unreachable, one index -> one decoder

    function integer wbin;
        input [4:0] w;
        begin
            if (w == 5'd4)      wbin = 0;
            else if (w <= 5'd8) wbin = 1;
            else if (w <= 5'd12) wbin = 2;
            else                wbin = 3;
        end
    endfunction

    function integer dbin;
        input [7:0] d;
        begin
            if (d == 8'd0)      dbin = 0;
            else if (d <= 8'd7) dbin = 1;
            else                dbin = 2;
        end
    endfunction

    task sample_cov;
        input cpol_i; input cpha_i; input lsb_i; input [4:0] w;
        input [7:0] d; input [1:0] dv;
        integer m, o, wb;
        begin
            m  = {cpol_i, cpha_i};
            o  = lsb_i ? 1 : 0;
            wb = wbin(w);
            cp_mode[m]        = cp_mode[m] + 1;
            cp_order[o]       = cp_order[o] + 1;
            cp_width[wb]      = cp_width[wb] + 1;
            cp_div[dbin(d)]   = cp_div[dbin(d)] + 1;
            cp_dev[dv]        = cp_dev[dv] + 1;
            x_mode_order[m*2 + o]   = x_mode_order[m*2 + o] + 1;
            x_mode_width[m*4 + wb]  = x_mode_width[m*4 + wb] + 1;
            x_width_order[wb*2 + o] = x_width_order[wb*2 + o] + 1;
        end
    endtask

    function integer holes_in;
        input integer which;   // 0..7 selects the coverpoint / cross
        integer i, h;
        begin
            h = 0;
            if (which == 0) for (i=0;i<4;i=i+1)  if (cp_mode[i]==0)       h=h+1;
            if (which == 1) for (i=0;i<2;i=i+1)  if (cp_order[i]==0)      h=h+1;
            if (which == 2) for (i=0;i<4;i=i+1)  if (cp_width[i]==0)      h=h+1;
            if (which == 3) for (i=0;i<3;i=i+1)  if (cp_div[i]==0)        h=h+1;
            if (which == 4) for (i=0;i<4;i=i+1)  if (cp_dev[i]==0)        h=h+1;
            if (which == 5) for (i=0;i<8;i=i+1)  if (x_mode_order[i]==0)  h=h+1;
            if (which == 6) for (i=0;i<16;i=i+1) if (x_mode_width[i]==0)  h=h+1;
            if (which == 7) for (i=0;i<8;i=i+1)  if (x_width_order[i]==0) h=h+1;
            holes_in = h;
        end
    endfunction

    // A TASK, not a function, and the reason is a Verilog-2001 rule that catches
    // everyone once: a FUNCTION MUST HAVE AT LEAST ONE INPUT. Written as
    // `function integer total_holes;` with no arguments it compiles under
    // SystemVerilog and fails under -g2001 with
    //
    //     error: Function total_holes has no ports.
    //     : Functions must have at least one input port.
    //
    // A task may have outputs only, so the total comes back through one.
    task total_holes;
        output integer h;
        integer i;
        begin
            h = 0;
            for (i = 0; i < 8; i = i + 1) h = h + holes_in(i);
        end
    endtask

    // ---------------- monitors -------------------------------------------------
    integer n_edge, n_frames, n_low;
    reg     sclk_d, cs_d;

    always @(posedge clk) begin
        if (!rst_n) begin
            n_edge <= 0; n_frames <= 0; sclk_d <= 1'b0; cs_d <= 1'b0;
        end else begin
            n_low = (cs_n[0]?0:1)+(cs_n[1]?0:1)+(cs_n[2]?0:1)+(cs_n[3]?0:1);
            if (n_low > 1) ex_two_selects = ex_two_selects + 1;
            if (cs_any && !cs_d) n_edge <= 0;
            if (!cs_any && cs_d) n_frames <= n_frames + 1;
                // `cs_d` as well as `cs_any`: an edge is only a FRAME edge if a device
                // was ALREADY selected last cycle. A transition in the very cycle the
                // select falls is SCLK reaching its new idle level, not a clocking
                // edge -- the controller parks SCLK and asserts CS together, so when
                // the previous idle level differed the two coincide.
                //
                // Measured cost of omitting `cs_d`: the first transaction after reset
                // with CPOL=1 counted 17 edges instead of 16 in VHDL and 16 in
                // SystemVerilog, because a one-cycle difference in reset-release
                // timing decided whether the re-park landed inside the window. The
                // received data was correct in both. With the gate the measurement no
                // longer depends on that phase at all.
            if (cs_any && cs_d && (sclk !== sclk_d)) n_edge <= n_edge + 1;
            cs_d <= cs_any; sclk_d <= sclk;
        end
    end

    // ---------------- stimulus -------------------------------------------------
    task set_cfg;
        input cpol_i; input cpha_i; input lsb_i; input [4:0] w;
        input [7:0] dv; input [1:0] dv_n;
        begin
            cfg_cpol=cpol_i; cfg_cpha=cpha_i; cfg_lsb=lsb_i;
            cfg_width=w; cfg_div=dv; cfg_dev=dv_n;
            cfg_lead=4'd2; cfg_lag=4'd2; cfg_idle=4'd1;
            slv_cpol=cpol_i; slv_cpha=cpha_i; slv_w=w;
        end
    endtask

    task fire;
        input [15:0] d;
        begin
            @(negedge clk); tx_data = d; start = 1'b1;
            @(negedge clk); start = 1'b0;
        end
    endtask

    task wait_idle;
        input integer maxc; output gotd;
        integer g; reg seen;
        begin
            g=0; seen=1'b0;
            while (g < maxc) begin
                @(negedge clk); g=g+1;
                if (done) seen=1'b1;
                if (!busy && seen) g=maxc;
                else if (!busy && g>4) g=maxc;
            end
            gotd = seen;
        end
    endtask

    // One transaction: build it, run it, score it, sample coverage.
    task do_txn;
        input cpol_i; input cpha_i; input lsb_i; input [4:0] w;
        input [7:0] dv; input [1:0] dv_n; input [15:0] txd; input [15:0] swd;
        reg gd;
        begin
            set_cfg(cpol_i, cpha_i, lsb_i, w, dv, dv_n);
            slv_word = swd & mask(w);
            fire(txd);
            wait_idle(40000, gd);
            n_chk = n_chk + 1;
            if (!gd) begin
                n_err = n_err + 1;
                $display("    FAIL no done for w=%0d div=%0d", w, dv);
            end
            n_chk = n_chk + 1;
            if (rx_data !== ref_rx(swd & mask(w), w, lsb_i)) begin
                n_err = n_err + 1;
                $display("    FAIL rx w=%0d mode=%0d lsb=%0d got %04h exp %04h",
                         w, {cpol_i,cpha_i}, lsb_i, rx_data,
                         ref_rx(swd & mask(w), w, lsb_i));
            end
            n_chk = n_chk + 1;
            if ((slv_rx & mask(w)) !== ref_slave_rx(txd, w, lsb_i)) begin
                n_err = n_err + 1;
                $display("    FAIL device rx w=%0d got %04h exp %04h",
                         w, slv_rx & mask(w), ref_slave_rx(txd, w, lsb_i));
            end
            n_chk = n_chk + 1;
            if (n_edge !== 2 * w) begin
                n_err = n_err + 1;
                $display("    FAIL edges w=%0d got %0d exp %0d", w, n_edge, 2*w);
            end
            sample_cov(cpol_i, cpha_i, lsb_i, w, dv, dv_n);
        end
    endtask

    integer i, j, k, m, o, wb;
    integer h_before, h_after, root_causes, targeted;
    integer lo_hist [0:3];
    integer hi_hist [0:3];
    integer smin, smax, sbad;
    integer v;
    reg gdx;

    initial begin
        clk=1'b0; rst_n=1'b0; start=1'b0; abort=1'b0; tx_data=16'd0;
        cfg_cpol=1'b0; cfg_cpha=1'b0; cfg_lsb=1'b0; cfg_width=5'd8;
        cfg_div=8'd1; cfg_dev=2'd0; cfg_lead=4'd2; cfg_lag=4'd2; cfg_idle=4'd1;
        slv_cpol=1'b0; slv_cpha=1'b0; slv_w=5'd8; slv_word=16'd0;
        slv_miso=1'b0; slv_sr=16'd0; slv_rx=16'd0; slv_idx=5'd0; slv_nrx=5'd0;
        n_chk=0; n_err=0; n_neg=0; n_edge=0; n_frames=0;
        sclk_d=1'b0; cs_d=1'b0;
        ex_illegal_accepted=0; ex_two_selects=0;
        for (i=0;i<4;i=i+1)  cp_mode[i]=0;
        for (i=0;i<2;i=i+1)  cp_order[i]=0;
        for (i=0;i<4;i=i+1)  cp_width[i]=0;
        for (i=0;i<3;i=i+1)  cp_div[i]=0;
        for (i=0;i<4;i=i+1)  cp_dev[i]=0;
        for (i=0;i<8;i=i+1)  x_mode_order[i]=0;
        for (i=0;i<16;i=i+1) x_mode_width[i]=0;
        for (i=0;i<8;i=i+1)  x_width_order[i]=0;
        for (i=0;i<4;i=i+1)  begin lo_hist[i]=0; hi_hist[i]=0; end

        $display("=== Chapter 20.6 -- constrained random and coverage closure ===");

        // -----------------------------------------------------------------
        // C1 -- review the generator BEFORE trusting anything it produces.
        // -----------------------------------------------------------------
        $display("  C1 generator review, 2000 draws");
        lcg = 32'h1234_5678;
        smin = 999; smax = -999; sbad = 0;
        for (i = 0; i < 2000; i = i + 1) begin
            step_lcg;
            // The trap: treat the draw as SIGNED, exactly as `$random` is.
            v = 4 + ($signed(lcg) % 13);
            if (v < smin) smin = v;
            if (v > smax) smax = v;
            if (v < 4 || v > 16) sbad = sbad + 1;
        end
        $display("    signed  4 + (s %% 13) : min %4d max %4d illegal %4d of 2000",
                 smin, smax, sbad);

        lcg = 32'h1234_5678;
        smin = 999; smax = -999; sbad = 0;
        for (i = 0; i < 2000; i = i + 1) begin
            step_lcg;
            v = 4 + (draw(lcg) % 13);
            if (v < smin) smin = v;
            if (v > smax) smax = v;
            if (v < 4 || v > 16) sbad = sbad + 1;
        end
        $display("    unsigned 4 + (hi %% 13) : min %4d max %4d illegal %4d of 2000",
                 smin, smax, sbad);

        // Trap 2: low bits vs high bits.
        lcg = 32'h1234_5678;
        for (i = 0; i < 2000; i = i + 1) begin
            step_lcg;
            lo_hist[lcg[1:0]]   = lo_hist[lcg[1:0]] + 1;
            hi_hist[lcg[17:16]] = hi_hist[lcg[17:16]] + 1;
        end
        $display("    low  bits[1:0]  histogram %4d %4d %4d %4d",
                 lo_hist[0], lo_hist[1], lo_hist[2], lo_hist[3]);
        $display("    high bits[17:16] histogram %4d %4d %4d %4d",
                 hi_hist[0], hi_hist[1], hi_hist[2], hi_hist[3]);
        lcg = 32'h1234_5678;
        $write("    low  bits[1:0]  sequence ");
        for (i = 0; i < 16; i = i + 1) begin step_lcg; $write("%0d ", lcg[1:0]); end
        $display("");
        lcg = 32'h1234_5678;
        $write("    high bits[17:16] sequence ");
        for (i = 0; i < 16; i = i + 1) begin step_lcg; $write("%0d ", lcg[17:16]); end
        $display("");

        // A uniform low-bit histogram must NOT be accepted as evidence: the sequence
        // has period 4. This is checked, not merely narrated.
        n_chk = n_chk + 1;
        if (lo_hist[0] == 500 && lo_hist[1] == 500 &&
            lo_hist[2] == 500 && lo_hist[3] == 500) begin
            n_neg = n_neg + 1;
            $display("    low bits are PERFECTLY uniform and have period 4 -- rejected");
        end else begin
            n_err = n_err + 1;
            $display("    FAIL expected a perfectly uniform low-bit histogram");
        end

        // Reset is RELEASED ON A NEGEDGE, for the same reason `start` is driven on one.
        // Releasing it on a posedge puts the assignment in the same region as every
        // clocked block that tests it, and the order is undefined: the monitor may see
        // the old value or the new one. Measured cost of getting this wrong -- the
        // monitor held its reset one cycle longer in VHDL than in SystemVerilog, so the
        // idle re-park of SCLK to CPOL=1 was counted as a frame edge in one language
        // and not the other, and the first transaction of the run reported 17 edges
        // instead of 16 in exactly one of the three.
        repeat (4) @(posedge clk);
        @(negedge clk); rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // -----------------------------------------------------------------
        // C2 -- PHASE A: 40 transactions under a deliberately NARROW constraint.
        // -----------------------------------------------------------------
        $display("  C2 phase A: 40 transactions, width 4..8, div 1..3");
        lcg = 32'hACE1_0F0F;
        for (i = 0; i < 40; i = i + 1) begin
            step_lcg; m  = draw(lcg) % 4;
            step_lcg; o  = draw(lcg) % 2;
            step_lcg; wb = 4 + (draw(lcg) % 5);          // 4..8 only
            step_lcg; k  = 1 + (draw(lcg) % 3);          // 1..3 only
            step_lcg; j  = draw(lcg) % 4;
            step_lcg; v  = draw(lcg);
            do_txn(m[1], m[0], o[0], wb[4:0], k[7:0], j[1:0],
                   {v[7:0], v[14:7]}, {v[3:0], v[14:3]});
        end
        total_holes(h_before);
        $display("    scored %0d transactions, %0d failures, %0d coverage holes",
                 40, n_err, h_before);

        // -----------------------------------------------------------------
        // C3 -- report the holes per coverpoint.
        // -----------------------------------------------------------------
        $display("  C3 coverage after phase A");
        $display("    cp_mode        %2d/%2d   holes %2d", 4-holes_in(0),  4, holes_in(0));
        $display("    cp_order       %2d/%2d   holes %2d", 2-holes_in(1),  2, holes_in(1));
        $display("    cp_width       %2d/%2d   holes %2d", 4-holes_in(2),  4, holes_in(2));
        $display("    cp_div         %2d/%2d   holes %2d", 3-holes_in(3),  3, holes_in(3));
        $display("    cp_dev         %2d/%2d   holes %2d", 4-holes_in(4),  4, holes_in(4));
        $display("    x_mode_order   %2d/%2d   holes %2d", 8-holes_in(5),  8, holes_in(5));
        $display("    x_mode_width   %2d/%2d   holes %2d", 16-holes_in(6),16, holes_in(6));
        $display("    x_width_order  %2d/%2d   holes %2d", 8-holes_in(7),  8, holes_in(7));

        // -----------------------------------------------------------------
        // C4 -- classify. This is the step that gets skipped, and it is the step
        // that decides whether the next run is useful.
        // -----------------------------------------------------------------
        $display("  C4 hole classification");
        root_causes = 0;
        if (cp_width[2] == 0 || cp_width[3] == 0) begin
            root_causes = root_causes + 1;
            $display("    CONSTRAINT BUG  width range 4..8 cannot reach bins 9-12, 13-16");
        end
        if (cp_div[0] == 0 || cp_div[2] == 0) begin
            root_causes = root_causes + 1;
            $display("    CONSTRAINT BUG  div range 1..3 cannot reach bin 0 or bin 8+");
        end
        $display("    DEPENDENT       %0d cross holes follow from the %0d above, not from",
                 holes_in(6) + holes_in(7), root_causes);
        $display("                    separate stimulus gaps -- fixing the ranges fixes them");
        n_chk = n_chk + 1;
        if (root_causes != 2) begin
            n_err = n_err + 1;
            $display("    FAIL expected 2 constraint root causes, found %0d", root_causes);
        end

        // -----------------------------------------------------------------
        // C5 -- PHASE B: widened constraints.
        // -----------------------------------------------------------------
        // Phase B widens WIDTH but deliberately leaves DIV at 0..7, so bin 8+ is
        // still unreachable by random draw. That is not an oversight in the example:
        // it is the normal end state of a random campaign. Random stimulus closes the
        // bulk cheaply and then stops improving, and the last few bins have to be
        // constructed. A closure loop whose targeted stage never runs has not been
        // shown to work -- and with 120 transactions and div 0..11 it did not run,
        // which is how this batch size was chosen.
        $display("  C5 phase B: 45 transactions, width 4..16, div 0..7");
        lcg = 32'h5EED_1111;
        for (i = 0; i < 45; i = i + 1) begin
            step_lcg; m  = draw(lcg) % 4;
            step_lcg; o  = draw(lcg) % 2;
            step_lcg; wb = 4 + (draw(lcg) % 13);         // 4..16
            step_lcg; k  = draw(lcg) % 8;                // 0..7 -- bin 8+ left open
            step_lcg; j  = draw(lcg) % 4;
            step_lcg; v  = draw(lcg);
            do_txn(m[1], m[0], o[0], wb[4:0], k[7:0], j[1:0],
                   {v[7:0], v[14:7]}, {v[3:0], v[14:3]});
        end
        total_holes(h_after);
        $display("    widened random leaves %0d holes", h_after);

        // -----------------------------------------------------------------
        // C6 -- TARGETED closure for whatever random did not reach. Constructed for
        // the specific bin, which is the only way to close a hole deterministically.
        // -----------------------------------------------------------------
        $display("  C6 targeted closure");
        targeted = 0;
        for (i = 0; i < 16; i = i + 1) begin
            if (x_mode_width[i] == 0) begin
                m  = i / 4;
                wb = i % 4;
                if      (wb == 0) k = 4;
                else if (wb == 1) k = 7;
                else if (wb == 2) k = 11;
                else              k = 15;
                do_txn(m[1], m[0], 1'b0, k[4:0], 8'd1, 2'd0, 16'h9D5A, 16'h3CB9);
                targeted = targeted + 1;
            end
        end
        for (i = 0; i < 8; i = i + 1) begin
            if (x_width_order[i] == 0) begin
                wb = i / 2;
                o  = i % 2;
                if      (wb == 0) k = 4;
                else if (wb == 1) k = 6;
                else if (wb == 2) k = 10;
                else              k = 14;
                do_txn(1'b0, 1'b0, o[0], k[4:0], 8'd1, 2'd0, 16'h9D5A, 16'h3CB9);
                targeted = targeted + 1;
            end
        end
        for (i = 0; i < 3; i = i + 1) begin
            if (cp_div[i] == 0) begin
                if      (i == 0) k = 0;
                else if (i == 1) k = 4;
                else             k = 9;
                do_txn(1'b0, 1'b0, 1'b0, 5'd8, k[7:0], 2'd0, 16'h9D5A, 16'h3CB9);
                targeted = targeted + 1;
            end
        end
        $display("    %0d targeted transactions added", targeted);

        // -----------------------------------------------------------------
        // C7 -- final coverage, and the two bins that are EXCLUDED with reasons.
        // -----------------------------------------------------------------
        $display("  C7 final coverage");
        $display("    cp_mode        %2d/%2d", 4-holes_in(0),  4);
        $display("    cp_order       %2d/%2d", 2-holes_in(1),  2);
        $display("    cp_width       %2d/%2d", 4-holes_in(2),  4);
        $display("    cp_div         %2d/%2d", 3-holes_in(3),  3);
        $display("    cp_dev         %2d/%2d", 4-holes_in(4),  4);
        $display("    x_mode_order   %2d/%2d", 8-holes_in(5),  8);
        $display("    x_mode_width  %2d/%2d", 16-holes_in(6), 16);
        $display("    x_width_order  %2d/%2d", 8-holes_in(7),  8);
        total_holes(h_after);
        $display("    remaining holes %0d", h_after);
        n_chk = n_chk + 1;
        if (h_after != 0) begin
            n_err = n_err + 1;
            $display("    FAIL %0d holes remain after targeted closure", h_after);
        end

        $display("    excluded, not chased:");
        $display("      illegal   width < 4 accepted   hits %0d  (REQ-ERR-001 forbids it)",
                 ex_illegal_accepted);
        $display("      unreach   two selects low      hits %0d  (one index, one decoder)",
                 ex_two_selects);
        n_chk = n_chk + 1;
        if (ex_illegal_accepted != 0 || ex_two_selects != 0) begin
            n_err = n_err + 1;
            $display("    FAIL an excluded bin was hit -- the exclusion was wrong");
        end

        // An illegal width is offered once, to prove the illegal bin's counter is
        // wired to something and is not simply a variable that stays at zero.
        set_cfg(1'b0, 1'b0, 1'b0, 5'd3, 8'd1, 2'd0);
        fire(16'h1234);
        if (busy) ex_illegal_accepted = ex_illegal_accepted + 1;
        n_chk = n_chk + 1;
        if (ex_illegal_accepted != 0) begin
            n_err = n_err + 1;
            $display("    FAIL an illegal width started a frame");
        end else begin
            n_neg = n_neg + 1;
            $display("      the illegal bin was offered a width of 3 and stayed 0");
        end

        $display("=== SUMMARY checks=%0d negatives=%0d failures=%0d : %0s ===",
                 n_chk, n_neg, n_err, (n_err == 0) ? "PASS" : "FAIL");
        $finish;
    end

    initial begin
        #40000000;
        $display("    FATAL global timeout");
        $display("=== SUMMARY checks=%0d negatives=%0d failures=%0d : FAIL ===",
                 n_chk, n_neg, n_err + 1);
        $finish;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_capstone_crv_tb.vhd — the same bench in VHDL, with the LCG reproduced bit for bit
-- spi_capstone_crv_tb.vhd
--
-- Chapter 20.6 in VHDL-2008.
--
-- THE GENERATOR IS REBUILT HERE BIT FOR BIT, and that is the whole reason this file
-- exists. A randomised bench written with each language's own random primitive
-- produces three different stimulus streams, and then "all three passed" means only
-- that three different tests passed -- the coverage tables cannot be compared at all.
--
-- The recurrence
--
--     s = s * 1664525 + 1013904223   (mod 2^32)
--
-- is reproduced exactly. Verilog truncates the product to 32 bits by context; VHDL's
-- `"*"` on two 32-bit unsigneds returns 64 bits, so the truncation is explicit here.
-- Those agree because (a*b mod 2^32 + c) mod 2^32 = (a*b + c) mod 2^32 -- and the
-- proof that they agree in practice is that all three transcripts, including every
-- coverage count and both trap measurements, come out identical.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use std.env.all;

entity spi_capstone_crv_tb is
end entity spi_capstone_crv_tb;

architecture tb of spi_capstone_crv_tb is

    signal clk       : std_logic := '0';
    signal rst_n     : std_logic := '0';
    signal cfg_cpol  : std_logic := '0';
    signal cfg_cpha  : std_logic := '0';
    signal cfg_lsb   : std_logic := '0';
    signal cfg_width : std_logic_vector(4 downto 0) := "01000";
    signal cfg_div   : std_logic_vector(7 downto 0) := x"01";
    signal cfg_dev   : std_logic_vector(1 downto 0) := "00";
    signal cfg_lead  : std_logic_vector(3 downto 0) := x"2";
    signal cfg_lag   : std_logic_vector(3 downto 0) := x"2";
    signal cfg_idle  : std_logic_vector(3 downto 0) := x"1";
    signal start     : std_logic := '0';
    signal abort     : std_logic := '0';
    signal tx_data   : std_logic_vector(15 downto 0) := (others => '0');
    signal busy      : std_logic;
    signal done      : std_logic;
    signal cfg_err   : std_logic;
    signal rx_data   : std_logic_vector(15 downto 0);
    signal bits_done : std_logic_vector(4 downto 0);
    signal sclk      : std_logic;
    signal mosi      : std_logic;
    signal cs_n      : std_logic_vector(3 downto 0);
    signal miso      : std_logic;

    signal slv_cpol  : std_logic := '0';
    signal slv_cpha  : std_logic := '0';
    signal slv_word  : std_logic_vector(15 downto 0) := (others => '0');
    signal slv_rx    : std_logic_vector(15 downto 0) := (others => '0');
    signal slv_w     : unsigned(4 downto 0) := to_unsigned(8, 5);
    signal slv_miso  : std_logic := '0';
    signal cs_any    : std_logic;

    signal n_edge        : integer := 0;
    signal ex_two_select : integer := 0;
    signal cs_d, sclk_d  : std_logic := '0';

    -- ---------------- generator ---------------------------------------------------
    function lcg_next (s : unsigned(31 downto 0)) return unsigned is
        variable p : unsigned(63 downto 0);
    begin
        p := s * to_unsigned(1664525, 32);
        p := p + to_unsigned(1013904223, 64);
        return p(31 downto 0);
    end function lcg_next;

    -- Draw from the HIGH half only. Bits 30..16, never the bottom ones.
    function draw (s : unsigned(31 downto 0)) return integer is
    begin
        return to_integer(s(30 downto 16));
    end function draw;

    -- ---------------- oracle ------------------------------------------------------
    function maskw (w : integer) return unsigned is
        variable m : unsigned(15 downto 0);
    begin
        m := (others => '0');
        for b in 0 to 15 loop
            if b < w then m(b) := '1'; end if;
        end loop;
        return m;
    end function maskw;

    function revw (v : std_logic_vector(15 downto 0); w : integer)
        return std_logic_vector is
        variable r : std_logic_vector(15 downto 0);
    begin
        r := (others => '0');
        for b in 0 to 15 loop
            if b < w then r(w-1-b) := v(b); end if;
        end loop;
        return r;
    end function revw;

    function ref_rx (sw : std_logic_vector(15 downto 0); w : integer; lsb : std_logic)
        return std_logic_vector is
        variable m : std_logic_vector(15 downto 0);
    begin
        m := std_logic_vector(unsigned(sw) and maskw(w));
        if lsb = '1' then return revw(m, w); else return m; end if;
    end function ref_rx;

    function ref_slave_rx (tx : std_logic_vector(15 downto 0); w : integer;
                           lsb : std_logic) return std_logic_vector is
        variable m : std_logic_vector(15 downto 0);
    begin
        m := std_logic_vector(unsigned(tx) and maskw(w));
        if lsb = '1' then return revw(m, w); else return m; end if;
    end function ref_slave_rx;

    function wbin (w : integer) return integer is
    begin
        if    w = 4  then return 0;
        elsif w <= 8 then return 1;
        elsif w <= 12 then return 2;
        else return 3; end if;
    end function wbin;

    function dbin (d : integer) return integer is
    begin
        if    d = 0  then return 0;
        elsif d <= 7 then return 1;
        else return 2; end if;
    end function dbin;

    -- ---------------- formatting --------------------------------------------------
    function hex4 (v : std_logic_vector(15 downto 0)) return string is
        constant D : string(1 to 16) := "0123456789abcdef";
        variable s : string(1 to 4);
        variable n : integer;
    begin
        if is_x(v) then return "xxxx"; end if;
        n := to_integer(unsigned(v));
        for i in 4 downto 1 loop
            s(i) := D((n mod 16) + 1);
            n := n / 16;
        end loop;
        return s;
    end function hex4;

    -- Right-aligned, and it HANDLES NEGATIVES: the signed-modulo measurement in group
    -- C1 prints a minimum of -8, and a formatter that silently dropped the sign would
    -- hide exactly the defect the group exists to show.
    function ipad (v : integer; w : integer) return string is
        variable s   : string(1 to w);
        variable t   : string(1 to 20);
        variable n, len : integer;
        variable neg : boolean;
    begin
        t := (others => ' ');
        neg := v < 0;
        if neg then n := -v; else n := v; end if;
        len := 0;
        if n = 0 then
            len := 1; t(1) := '0';
        else
            while n > 0 loop
                len := len + 1;
                t(len) := character'val(character'pos('0') + (n mod 10));
                n := n / 10;
            end loop;
        end if;
        if neg then
            len := len + 1; t(len) := '-';
        end if;
        s := (others => ' ');
        for i in 1 to len loop
            s(w - i + 1) := t(i);
        end loop;
        return s;
    end function ipad;

    function i0 (v : integer) return string is
    begin
        return integer'image(v);
    end function i0;

    procedure pr (s : string) is
        variable l : line;
    begin
        write(l, s);
        writeline(output, l);
    end procedure pr;

    function sl (b : boolean) return std_logic is
    begin
        if b then return '1'; else return '0'; end if;
    end function sl;

begin

    cs_any <= not (cs_n(0) and cs_n(1) and cs_n(2) and cs_n(3));
    miso   <= slv_miso;

    dut : entity work.spi_capstone_ctrl
        generic map (DATA_W => 16, MIN_WIDTH => 4, NDEV => 4)
        port map (
            clk => clk, rst_n => rst_n,
            cfg_cpol => cfg_cpol, cfg_cpha => cfg_cpha, cfg_lsb_first => cfg_lsb,
            cfg_width => cfg_width, cfg_div => cfg_div, cfg_dev => cfg_dev,
            cfg_lead => cfg_lead, cfg_lag => cfg_lag, cfg_idle => cfg_idle,
            start => start, tx_data => tx_data, abort => abort,
            busy => busy, done => done, cfg_err => cfg_err,
            rx_data => rx_data, bits_done => bits_done,
            sclk => sclk, mosi => mosi, cs_n => cs_n, miso => miso
        );

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

    slave : process (cs_any, sclk)
        variable sr     : std_logic_vector(15 downto 0);
        variable idx    : unsigned(4 downto 0);
        variable nrx    : unsigned(4 downto 0);
        variable lead_s : boolean;
    begin
        if rising_edge(cs_any) then
            sr := std_logic_vector(shift_left(unsigned(slv_word),
                                              16 - to_integer(slv_w)));
            slv_rx <= (others => '0');
            nrx    := (others => '0');
            if slv_cpha = '0' then
                slv_miso <= sr(15);
                sr       := sr(14 downto 0) & '0';
                idx      := to_unsigned(1, 5);
            else
                slv_miso <= '0';
                idx      := (others => '0');
            end if;
        elsif sclk'event and cs_any = '1' then
            lead_s := (sclk /= slv_cpol);
            if (slv_cpha = '1' and not lead_s) or (slv_cpha = '0' and lead_s) then
                if nrx < slv_w then
                    slv_rx <= slv_rx(14 downto 0) & mosi;
                    nrx    := nrx + 1;
                end if;
            end if;
            if (slv_cpha = '1' and lead_s) or (slv_cpha = '0' and not lead_s) then
                if idx < slv_w then
                    slv_miso <= sr(15);
                    sr       := sr(14 downto 0) & '0';
                    idx      := idx + 1;
                end if;
            end if;
        end if;
    end process slave;

    mon : process (clk)
        variable n_low : integer;
    begin
        if rising_edge(clk) then
            if rst_n = '0' then
                n_edge <= 0; cs_d <= '0'; sclk_d <= '0'; ex_two_select <= 0;
            else
                n_low := 0;
                for i in 0 to 3 loop
                    if cs_n(i) = '0' then n_low := n_low + 1; end if;
                end loop;
                if n_low > 1 then ex_two_select <= ex_two_select + 1; end if;
                if cs_any = '1' and cs_d = '0' then n_edge <= 0; end if;
                -- `cs_d` as well as `cs_any`: an edge is only a FRAME edge if a device
                -- was ALREADY selected last cycle. A transition in the cycle the
                -- select falls is SCLK reaching its new idle level, not a clocking
                -- edge. Without this gate the first CPOL=1 transaction counted 17
                -- edges here and 16 in SystemVerilog, on identical data.
                if cs_any = '1' and cs_d = '1' and sclk /= sclk_d then
                    n_edge <= n_edge + 1;
                end if;
                cs_d   <= cs_any;
                sclk_d <= sclk;
            end if;
        end if;
    end process mon;

    wd : process
    begin
        wait for 400 ms;
        pr("    FATAL global timeout");
        pr("=== SUMMARY checks=0 negatives=0 failures=1 : FAIL ===");
        finish;
    end process wd;

    main : process
        variable n_chk, n_err, n_neg : integer := 0;
        variable lcg  : unsigned(31 downto 0);
        variable vv   : unsigned(14 downto 0);
        variable smin, smax, sbad, v : integer;
        variable m, o, wv, kv, jv    : integer;
        variable h_before, h_after, root_causes, targeted : integer;
        variable lo_hist, hi_hist : integer_vector(0 to 3);
        variable cp_mode, cp_dev  : integer_vector(0 to 3);
        variable cp_order         : integer_vector(0 to 1);
        variable cp_width         : integer_vector(0 to 3);
        variable cp_div           : integer_vector(0 to 2);
        variable x_mode_order     : integer_vector(0 to 7);
        variable x_mode_width     : integer_vector(0 to 15);
        variable x_width_order    : integer_vector(0 to 7);
        variable ex_illegal       : integer;
        variable d32              : string(1 to 32);
        variable gd               : std_logic;

        procedure sample_cov (cpol_i, cpha_i, lsb_i : std_logic;
                              w, d, dv : integer) is
            variable mm, oo, wb : integer;
        begin
            mm := 0;
            if cpol_i = '1' then mm := mm + 2; end if;
            if cpha_i = '1' then mm := mm + 1; end if;
            if lsb_i = '1' then oo := 1; else oo := 0; end if;
            wb := wbin(w);
            cp_mode(mm)  := cp_mode(mm) + 1;
            cp_order(oo) := cp_order(oo) + 1;
            cp_width(wb) := cp_width(wb) + 1;
            cp_div(dbin(d)) := cp_div(dbin(d)) + 1;
            cp_dev(dv)   := cp_dev(dv) + 1;
            x_mode_order(mm*2 + oo)  := x_mode_order(mm*2 + oo) + 1;
            x_mode_width(mm*4 + wb)  := x_mode_width(mm*4 + wb) + 1;
            x_width_order(wb*2 + oo) := x_width_order(wb*2 + oo) + 1;
        end procedure sample_cov;

        function holes_in (which : integer;
                           a4 : integer_vector(0 to 3);
                           a2 : integer_vector(0 to 1);
                           a3 : integer_vector(0 to 2);
                           a8 : integer_vector(0 to 7);
                           a16 : integer_vector(0 to 15)) return integer is
            variable h : integer := 0;
        begin
            case which is
                when 0 | 2 | 4 => for i in 0 to 3  loop if a4(i)  = 0 then h := h+1; end if; end loop;
                when 1         => for i in 0 to 1  loop if a2(i)  = 0 then h := h+1; end if; end loop;
                when 3         => for i in 0 to 2  loop if a3(i)  = 0 then h := h+1; end if; end loop;
                when 5 | 7     => for i in 0 to 7  loop if a8(i)  = 0 then h := h+1; end if; end loop;
                when others    => for i in 0 to 15 loop if a16(i) = 0 then h := h+1; end if; end loop;
            end case;
            return h;
        end function holes_in;

        -- Local wrappers so the call sites read like the other two languages.
        impure function h0 return integer is begin
            return holes_in(0, cp_mode, cp_order, cp_div, x_mode_order, x_mode_width);
        end function;
        impure function h1 return integer is
            variable h : integer := 0;
        begin
            for i in 0 to 1 loop if cp_order(i) = 0 then h := h+1; end if; end loop;
            return h;
        end function;
        impure function h2 return integer is
            variable h : integer := 0;
        begin
            for i in 0 to 3 loop if cp_width(i) = 0 then h := h+1; end if; end loop;
            return h;
        end function;
        impure function h3 return integer is
            variable h : integer := 0;
        begin
            for i in 0 to 2 loop if cp_div(i) = 0 then h := h+1; end if; end loop;
            return h;
        end function;
        impure function h4 return integer is
            variable h : integer := 0;
        begin
            for i in 0 to 3 loop if cp_dev(i) = 0 then h := h+1; end if; end loop;
            return h;
        end function;
        impure function h5 return integer is
            variable h : integer := 0;
        begin
            for i in 0 to 7 loop if x_mode_order(i) = 0 then h := h+1; end if; end loop;
            return h;
        end function;
        impure function h6 return integer is
            variable h : integer := 0;
        begin
            for i in 0 to 15 loop if x_mode_width(i) = 0 then h := h+1; end if; end loop;
            return h;
        end function;
        impure function h7 return integer is
            variable h : integer := 0;
        begin
            for i in 0 to 7 loop if x_width_order(i) = 0 then h := h+1; end if; end loop;
            return h;
        end function;
        impure function htot return integer is
        begin
            return h0 + h1 + h2 + h3 + h4 + h5 + h6 + h7;
        end function;

        procedure set_cfg (cpol_i, cpha_i, lsb_i : std_logic;
                           wv2, dv2, dvn2 : integer) is
        begin
            cfg_cpol  <= cpol_i;
            cfg_cpha  <= cpha_i;
            cfg_lsb   <= lsb_i;
            cfg_width <= std_logic_vector(to_unsigned(wv2, 5));
            cfg_div   <= std_logic_vector(to_unsigned(dv2, 8));
            cfg_dev   <= std_logic_vector(to_unsigned(dvn2, 2));
            cfg_lead  <= x"2";
            cfg_lag   <= x"2";
            cfg_idle  <= x"1";
            slv_cpol  <= cpol_i;
            slv_cpha  <= cpha_i;
            slv_w     <= to_unsigned(wv2, 5);
        end procedure set_cfg;

        procedure fire (d : std_logic_vector(15 downto 0)) is
        begin
            wait until falling_edge(clk);
            tx_data <= d;
            start   <= '1';
            wait until falling_edge(clk);
            start   <= '0';
        end procedure fire;

        procedure wait_idle (maxc : integer; gotd : out std_logic) is
            variable g    : integer;
            variable seen : std_logic;
        begin
            g := 0; seen := '0';
            while g < maxc loop
                wait until falling_edge(clk);
                g := g + 1;
                if done = '1' then seen := '1'; end if;
                if busy = '0' and seen = '1' then g := maxc;
                elsif busy = '0' and g > 4 then g := maxc; end if;
            end loop;
            gotd := seen;
        end procedure wait_idle;

        procedure do_txn (cpol_i, cpha_i, lsb_i : std_logic;
                          w, d, dv : integer;
                          txd, swd : std_logic_vector(15 downto 0)) is
            variable g : std_logic;
        begin
            set_cfg(cpol_i, cpha_i, lsb_i, w, d, dv);
            slv_word <= std_logic_vector(unsigned(swd) and maskw(w));
            fire(txd);
            wait_idle(40000, g);
            n_chk := n_chk + 1;
            if g /= '1' then
                n_err := n_err + 1;
                pr("    FAIL no done for w=" & i0(w) & " div=" & i0(d));
            end if;
            n_chk := n_chk + 1;
            if rx_data /= ref_rx(std_logic_vector(unsigned(swd) and maskw(w)),
                                 w, lsb_i) then
                n_err := n_err + 1;
                pr("    FAIL rx w=" & i0(w) & " got " & hex4(rx_data));
            end if;
            n_chk := n_chk + 1;
            if (std_logic_vector(unsigned(slv_rx) and maskw(w)))
               /= ref_slave_rx(txd, w, lsb_i) then
                n_err := n_err + 1;
                pr("    FAIL device rx w=" & i0(w));
            end if;
            n_chk := n_chk + 1;
            if n_edge /= 2 * w then
                n_err := n_err + 1;
                pr("    FAIL edges w=" & i0(w) & " got " & i0(n_edge));
            end if;
            sample_cov(cpol_i, cpha_i, lsb_i, w, d, dv);
        end procedure do_txn;

    begin
        n_chk := 0; n_err := 0; n_neg := 0;
        cp_mode := (others => 0); cp_order := (others => 0);
        cp_width := (others => 0); cp_div := (others => 0);
        cp_dev := (others => 0); x_mode_order := (others => 0);
        x_mode_width := (others => 0); x_width_order := (others => 0);
        lo_hist := (others => 0); hi_hist := (others => 0);
        ex_illegal := 0;

        pr("=== Chapter 20.6 -- constrained random and coverage closure ===");

        pr("  C1 generator review, 2000 draws");
        lcg := x"12345678";
        smin := 999; smax := -999; sbad := 0;
        for i in 0 to 1999 loop
            lcg := lcg_next(lcg);
            v := 4 + (to_integer(signed(lcg)) rem 13);
            if v < smin then smin := v; end if;
            if v > smax then smax := v; end if;
            if v < 4 or v > 16 then sbad := sbad + 1; end if;
        end loop;
        pr("    signed  4 + (s % 13) : min " & ipad(smin,4) & " max " & ipad(smax,4) &
           " illegal " & ipad(sbad,4) & " of 2000");

        lcg := x"12345678";
        smin := 999; smax := -999; sbad := 0;
        for i in 0 to 1999 loop
            lcg := lcg_next(lcg);
            v := 4 + (draw(lcg) mod 13);
            if v < smin then smin := v; end if;
            if v > smax then smax := v; end if;
            if v < 4 or v > 16 then sbad := sbad + 1; end if;
        end loop;
        pr("    unsigned 4 + (hi % 13) : min " & ipad(smin,4) & " max " & ipad(smax,4) &
           " illegal " & ipad(sbad,4) & " of 2000");

        lcg := x"12345678";
        for i in 0 to 1999 loop
            lcg := lcg_next(lcg);
            v := to_integer(lcg(1 downto 0));   lo_hist(v) := lo_hist(v) + 1;
            v := to_integer(lcg(17 downto 16)); hi_hist(v) := hi_hist(v) + 1;
        end loop;
        pr("    low  bits[1:0]  histogram " & ipad(lo_hist(0),4) & " " &
           ipad(lo_hist(1),4) & " " & ipad(lo_hist(2),4) & " " & ipad(lo_hist(3),4));
        pr("    high bits[17:16] histogram " & ipad(hi_hist(0),4) & " " &
           ipad(hi_hist(1),4) & " " & ipad(hi_hist(2),4) & " " & ipad(hi_hist(3),4));

        lcg := x"12345678";
        for i in 0 to 15 loop
            lcg := lcg_next(lcg);
            d32(2*i+1) := character'val(character'pos('0') +
                                        to_integer(lcg(1 downto 0)));
            d32(2*i+2) := ' ';
        end loop;
        pr("    low  bits[1:0]  sequence " & d32);
        lcg := x"12345678";
        for i in 0 to 15 loop
            lcg := lcg_next(lcg);
            d32(2*i+1) := character'val(character'pos('0') +
                                        to_integer(lcg(17 downto 16)));
            d32(2*i+2) := ' ';
        end loop;
        pr("    high bits[17:16] sequence " & d32);

        n_chk := n_chk + 1;
        if lo_hist(0) = 500 and lo_hist(1) = 500 and
           lo_hist(2) = 500 and lo_hist(3) = 500 then
            n_neg := n_neg + 1;
            pr("    low bits are PERFECTLY uniform and have period 4 -- rejected");
        else
            n_err := n_err + 1;
            pr("    FAIL expected a perfectly uniform low-bit histogram");
        end if;

        -- Reset is RELEASED ON A FALLING EDGE, for the same reason `start` is driven on
        -- one: released on a rising edge it races every clocked block that tests it.
        -- The measured cost was a one-cycle difference in when the monitor left reset,
        -- which counted SCLK's idle re-park as a frame edge in VHDL but not in
        -- SystemVerilog -- 17 edges against 16, on the first transaction only.
        for i in 1 to 4 loop wait until rising_edge(clk); end loop;
        wait until falling_edge(clk);
        rst_n <= '1';
        for i in 1 to 2 loop wait until rising_edge(clk); end loop;

        pr("  C2 phase A: 40 transactions, width 4..8, div 1..3");
        lcg := x"ACE10F0F";
        for i in 0 to 39 loop
            lcg := lcg_next(lcg); m  := draw(lcg) mod 4;
            lcg := lcg_next(lcg); o  := draw(lcg) mod 2;
            lcg := lcg_next(lcg); wv := 4 + (draw(lcg) mod 5);
            lcg := lcg_next(lcg); kv := 1 + (draw(lcg) mod 3);
            lcg := lcg_next(lcg); jv := draw(lcg) mod 4;
            lcg := lcg_next(lcg); vv := lcg(30 downto 16);
            do_txn(sl(m / 2 = 1), sl(m mod 2 = 1), sl(o = 1), wv, kv, jv,
                   std_logic_vector(vv(7 downto 0) & vv(14 downto 7)),
                   std_logic_vector(vv(3 downto 0) & vv(14 downto 3)));
        end loop;
        h_before := htot;
        pr("    scored 40 transactions, " & i0(n_err) & " failures, " &
           i0(h_before) & " coverage holes");

        pr("  C3 coverage after phase A");
        pr("    cp_mode        " & ipad(4-h0,2)  & "/" & ipad(4,2)  & "   holes " & ipad(h0,2));
        pr("    cp_order       " & ipad(2-h1,2)  & "/" & ipad(2,2)  & "   holes " & ipad(h1,2));
        pr("    cp_width       " & ipad(4-h2,2)  & "/" & ipad(4,2)  & "   holes " & ipad(h2,2));
        pr("    cp_div         " & ipad(3-h3,2)  & "/" & ipad(3,2)  & "   holes " & ipad(h3,2));
        pr("    cp_dev         " & ipad(4-h4,2)  & "/" & ipad(4,2)  & "   holes " & ipad(h4,2));
        pr("    x_mode_order   " & ipad(8-h5,2)  & "/" & ipad(8,2)  & "   holes " & ipad(h5,2));
        pr("    x_mode_width   " & ipad(16-h6,2) & "/" & ipad(16,2) & "   holes " & ipad(h6,2));
        pr("    x_width_order  " & ipad(8-h7,2)  & "/" & ipad(8,2)  & "   holes " & ipad(h7,2));

        pr("  C4 hole classification");
        root_causes := 0;
        if cp_width(2) = 0 or cp_width(3) = 0 then
            root_causes := root_causes + 1;
            pr("    CONSTRAINT BUG  width range 4..8 cannot reach bins 9-12, 13-16");
        end if;
        if cp_div(0) = 0 or cp_div(2) = 0 then
            root_causes := root_causes + 1;
            pr("    CONSTRAINT BUG  div range 1..3 cannot reach bin 0 or bin 8+");
        end if;
        pr("    DEPENDENT       " & i0(h6 + h7) &
           " cross holes follow from the " & i0(root_causes) & " above, not from");
        pr("                    separate stimulus gaps -- fixing the ranges fixes them");
        n_chk := n_chk + 1;
        if root_causes /= 2 then
            n_err := n_err + 1;
            pr("    FAIL expected 2 constraint root causes, found " & i0(root_causes));
        end if;

        pr("  C5 phase B: 45 transactions, width 4..16, div 0..7");
        lcg := x"5EED1111";
        for i in 0 to 44 loop
            lcg := lcg_next(lcg); m  := draw(lcg) mod 4;
            lcg := lcg_next(lcg); o  := draw(lcg) mod 2;
            lcg := lcg_next(lcg); wv := 4 + (draw(lcg) mod 13);
            lcg := lcg_next(lcg); kv := draw(lcg) mod 8;
            lcg := lcg_next(lcg); jv := draw(lcg) mod 4;
            lcg := lcg_next(lcg); vv := lcg(30 downto 16);
            do_txn(sl(m / 2 = 1), sl(m mod 2 = 1), sl(o = 1), wv, kv, jv,
                   std_logic_vector(vv(7 downto 0) & vv(14 downto 7)),
                   std_logic_vector(vv(3 downto 0) & vv(14 downto 3)));
        end loop;
        h_after := htot;
        pr("    widened random leaves " & i0(h_after) & " holes");

        pr("  C6 targeted closure");
        targeted := 0;
        for i in 0 to 15 loop
            if x_mode_width(i) = 0 then
                m := i / 4;
                case i mod 4 is
                    when 0      => kv := 4;
                    when 1      => kv := 7;
                    when 2      => kv := 11;
                    when others => kv := 15;
                end case;
                do_txn(sl(m / 2 = 1), sl(m mod 2 = 1), '0', kv, 1, 0,
                       x"9D5A", x"3CB9");
                targeted := targeted + 1;
            end if;
        end loop;
        for i in 0 to 7 loop
            if x_width_order(i) = 0 then
                o := i mod 2;
                case i / 2 is
                    when 0      => kv := 4;
                    when 1      => kv := 6;
                    when 2      => kv := 10;
                    when others => kv := 14;
                end case;
                do_txn('0', '0', sl(o = 1), kv, 1, 0, x"9D5A", x"3CB9");
                targeted := targeted + 1;
            end if;
        end loop;
        for i in 0 to 2 loop
            if cp_div(i) = 0 then
                case i is
                    when 0      => kv := 0;
                    when 1      => kv := 4;
                    when others => kv := 9;
                end case;
                do_txn('0', '0', '0', 8, kv, 0, x"9D5A", x"3CB9");
                targeted := targeted + 1;
            end if;
        end loop;
        pr("    " & i0(targeted) & " targeted transactions added");

        pr("  C7 final coverage");
        pr("    cp_mode        " & ipad(4-h0,2)  & "/" & ipad(4,2));
        pr("    cp_order       " & ipad(2-h1,2)  & "/" & ipad(2,2));
        pr("    cp_width       " & ipad(4-h2,2)  & "/" & ipad(4,2));
        pr("    cp_div         " & ipad(3-h3,2)  & "/" & ipad(3,2));
        pr("    cp_dev         " & ipad(4-h4,2)  & "/" & ipad(4,2));
        pr("    x_mode_order   " & ipad(8-h5,2)  & "/" & ipad(8,2));
        pr("    x_mode_width  " & ipad(16-h6,2) & "/" & ipad(16,2));
        pr("    x_width_order  " & ipad(8-h7,2)  & "/" & ipad(8,2));
        h_after := htot;
        pr("    remaining holes " & i0(h_after));
        n_chk := n_chk + 1;
        if h_after /= 0 then
            n_err := n_err + 1;
            pr("    FAIL " & i0(h_after) & " holes remain after targeted closure");
        end if;

        pr("    excluded, not chased:");
        pr("      illegal   width < 4 accepted   hits " & i0(ex_illegal) &
           "  (REQ-ERR-001 forbids it)");
        pr("      unreach   two selects low      hits " & i0(ex_two_select) &
           "  (one index, one decoder)");
        n_chk := n_chk + 1;
        if ex_illegal /= 0 or ex_two_select /= 0 then
            n_err := n_err + 1;
            pr("    FAIL an excluded bin was hit -- the exclusion was wrong");
        end if;

        set_cfg('0', '0', '0', 3, 1, 0);
        fire(x"1234");
        if busy = '1' then ex_illegal := ex_illegal + 1; end if;
        n_chk := n_chk + 1;
        if ex_illegal /= 0 then
            n_err := n_err + 1;
            pr("    FAIL an illegal width started a frame");
        else
            n_neg := n_neg + 1;
            pr("      the illegal bin was offered a width of 3 and stayed 0");
        end if;

        if n_err = 0 then
            pr("=== SUMMARY checks=" & i0(n_chk) & " negatives=" & i0(n_neg) &
               " failures=" & i0(n_err) & " : PASS ===");
        else
            pr("=== SUMMARY checks=" & i0(n_chk) & " negatives=" & i0(n_neg) &
               " failures=" & i0(n_err) & " : FAIL ===");
        end if;
        finish;
    end process main;

end architecture tb;

11. What It Reports

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== SUMMARY checks=353 negatives=2 failures=0 : PASS ===

353 checks in each of three languages, byte-identical transcripts, 160 transactions through the same scoreboard and property monitors Chapter 20.5 built, and zero functional failures.

Value
Transactions40 phase A + 45 phase B + 2 targeted
Checks per language353
Coverpoints / crosses5 / 3
Bins in the denominator49
Holes after phase A17, from 2 root causes
Holes after phase B2
Holes after targeted closure0
Excluded bins2, both asserted empty
Generator traps measured2

12. Summary

The generator was reviewed before it was trusted, and both traps in it were measured rather than warned about. Treating the draw as signed put 891 of 2000 widths outside the legal range — 44.6%, from a constraint that reads as 4 to 16. Drawing from the low bits gave a histogram of exactly 500/500/500/500 over a sequence whose period is four: perfectly uniform, entirely deterministic, and preferable to the real random source by any histogram-based review.

Closure ran as a loop rather than as a number. Phase A's narrow constraints left 17 holes from 2 root causes, and thirteen of those holes were arithmetic consequences of the two — so the work was one constraint change, not thirteen directed tests. Phase B's widened constraints left 2, both of them a divider bin the constraint deliberately could not reach, and both closed by transactions constructed for the specific bin. Two further bins are excluded from the denominator with recorded reasons, and the illegal one is offered an illegal width at the end of the run to prove its counter is wired to something.

The batch size in phase B was reduced from 120 to 45 for a reason worth keeping: at 120, random closed everything and the targeted stage never ran, which made a demonstrated loop into untested code.

Code coverage and functional coverage answer different questions and neither is verification. This controller's mode decoder is three continuous assignments, so a suite running one mode-0 frame reports 100% statement coverage on the logic most likely to be wrong — and CPOL appears nowhere in those lines, so no code-coverage metric can distinguish mode 0 from mode 2. The functional model catches it and has its own blind spots: no bin for an abort, a mid-frame reset, back-to-back transfers, or the state machine's default arm.

13. What Comes Next

Chapter 20.7 stops trusting all of it and breaks the design on purpose — thirteen deliberate defects, one detection matrix, and a loopback bench included specifically because it passes nine of them.

Continue learning