SPI · Module 13
From Protocol to Design Requirements
Turning a device transaction specification into RTL requirements a simulator can disagree with: why every requirement needs an independent violation, why measured intervals beat asserted ones, and the six properties an SPI master must satisfy.
Chapter 10.6 ended with a transaction specification: a machine-readable description of what a device expects on the wire. This module builds the master that produces it. The first question is not how — it is what would count as done.
"The master must assert chip select before the first clock edge." Is that a requirement?
It reads like one. It is not, and the difference is the whole of this chapter: there is no value it could take that a simulator could disagree with. How long before? Measured how? Reported by what? A sentence that cannot fail is documentation, not a requirement.
1. What a Transaction Spec Does Not Tell You
The specification from Module 10 describes a transaction: a sequence of bytes, a mode, a set of intervals in nanoseconds. It is a complete description of what the device needs.
It is not a description of what the master must do, and the gap is larger than it looks:
the transaction spec says the design must decide
------------------------------------ --------------------------------------
mode 0 how CPOL and CPHA reach the datapath
SCLK <= 20 MHz what divisor, and of which clock
t_CSS >= 5 ns how many system clocks that is
8-bit frames, MSB first where the alignment happens
CS held across 5 bytes what holds it, and what could drop it
t_CSD >= 50 ns between transactions which state pays itEvery line on the left is a fact about the device. Every line on the right is a decision, and a decision that can be made wrongly. The transaction spec cannot tell you whether the design made it correctly, because it describes an interface, not an implementation.
So before any RTL, the specification has to be restated as a set of properties about the pins the master drives — because the pins are the only thing both sides agree about.
2. Three Things That Make a Requirement Testable
A requirement is testable when three things are true of it, and it is worth being pedantic about each, because a requirement that fails any one of them will be marked "verified" by a suite that never exercised it.
It must be observable on signals that exist. "The FSM must be in the LEAD state for at least 5 cycles" is not a requirement about the master — it is a requirement about a particular implementation of the master, and it cannot be checked against a design that solves the problem differently. "Chip select must be low for at least 5 cycles before the first SCLK edge" is the same intent stated about pins, and survives any implementation.
It must have an independent violation. There must exist a waveform that breaks this requirement and no other. If every waveform that breaks requirement B also breaks requirement A, then B is not a separate requirement and a suite that checks A will silently claim to have checked B. This is subtler than it sounds and §5 works through a case where it fails.
It must be measured, not assumed. A requirement with a number in it needs the number measured from the waveform and compared. A monitor that asserts "the lead time was correct" by reproducing the design's own arithmetic will agree with the design whatever the design does, which is the most expensive kind of passing test.
3. The Six Requirements
Restating the transaction spec as pin properties gives six, and they are the complete set for a single-slave master. Each is numbered because the monitor in §6 publishes one sticky bit per requirement, and the numbering is the contract between the requirement document and the RTL.
R0 SCLK rests at CPOL whenever no transfer is in progress
R1 no SCLK edge occurs while chip select is inactive
R2 chip select leads the first SCLK edge by at least LEAD cycles
R3 chip select lags the last SCLK edge by at least LAG cycles
R4 each assertion of chip select carries exactly LEN SCLK periods
R5 consecutive assertions are separated by at least GAP cyclesRead them again and notice what they have in common: every one mentions only sclk, cs_n and a number. None mentions a state, a counter, a register or a strobe. That is deliberate — it is what lets the same monitor be pointed at three different implementations in three different languages, which is exactly what happens in this module.
R0 and R1 are the two halves of "quiet when idle". R0 is about the level and R1 is about transitions. A design can get one right and the other wrong: a master that parks SCLK at the wrong level satisfies R1 perfectly, and a master that glitches SCLK once at the correct level satisfies R0.
R2 and R3 are the datasheet's setup and hold, converted to cycles. The conversion is the designer's job and the monitor's job is only to check the result, which is why the monitor takes LEAD and LAG as parameters rather than nanoseconds.
R4 is the one most often left out. A master that sends nine clocks in a frame satisfies every timing requirement above and corrupts every transfer. It belongs in the list precisely because it is not a timing property and so does not appear in the timing section of any datasheet.
R5 is about the boundary between transactions, and it is the requirement that only exists because transactions come in sequences. A suite that runs one transaction per simulation cannot fail it.
4. What Each Requirement Costs When It Is Missing
It is worth attaching a consequence to each, because a requirement with no consequence attached tends to get traded away under schedule pressure:
R0 violated the slave sees a clock edge at chip-select assertion;
in CPHA=0 that edge is a capture, so the first bit is
sampled before the master has driven it
R1 violated a deselected slave that decodes on edges alone advances
its internal state; on a shared bus (Chapter 8.2) the
wrong device responds to the next transaction
R2 violated the slave's setup time is missed; works at low SCLK,
fails at high SCLK, and reads as signal integrity
R3 violated the slave's hold time is missed; the final bit is lost
and only the final bit, which looks like a data bug
R4 violated every frame is misaligned by the surplus or deficit;
a flash returns 0xFF and a sensor returns plausible
nonsense
R5 violated the second transaction is ignored entirely, and the
first one's result is returned againTwo of these — R2 and R5 — produce intermittent failures that depend on frequency or on timing between unrelated pieces of software. Those are the expensive ones, and they are the ones a requirements monitor earns its keep on, because they are nearly impossible to find by inspection.
5. The Trap: R0 and R1 Are Not Independent By Default
Requirement R1 says no edge may occur while chip select is inactive. Requirement R0 says SCLK must rest at CPOL while idle. Now consider how you would provoke R0 alone.
The obvious stimulus is: drive SCLK to the wrong level while chip select is high. But arriving at the wrong level is a transition, so that stimulus violates R1 too — and a monitor that reports both will look correct while proving nothing about R0's own logic. If R0's check were deleted entirely, this test would still fail on R1, and the coverage report would still be green.
The fix is to separate arrival from residence:
cycle 1..2 drive SCLK to the wrong level -> R1 fires (a transition)
cycle 3 clear the monitor's sticky bits -> both R0 and R1 clear
cycle 4..20 HOLD SCLK at the wrong level -> only R0 can fireAfter the clear there are no further transitions, so R1 has nothing to detect; SCLK is simply resting in the wrong place, which is R0 and only R0. If R0's logic were missing, this sequence would pass.
6. Building the Requirements Monitor — Three HDLs
The circuit
The monitor is a synthesisable block that watches sclk and cs_n and nothing else. It publishes:
- six sticky violation bits,
R0throughR5, one per requirement, each set on first violation and cleared only byclearor reset; - three measured intervals,
meas_lead,meas_lagandmeas_gap, in system clocks, taken from the waveform rather than computed from the parameters.
The measured outputs are the part that matters most and the part most often left out. A monitor that only reports pass or fail tells you that something is wrong; a monitor that reports "the lead was 4 cycles and 5 were required" tells you what is wrong and by how much, and the difference is an afternoon.
There are three implementation details worth stating before the code, because each was a bug first:
Interval counters reload with one, not zero. A counter that restarts at zero on the reference edge has counted zero cycles at the moment one cycle has elapsed, so every measured interval comes out one short — and a master that supplies exactly the required lead is reported as violating it. The counter is reloaded with 1 because the cycle on which it is reloaded is itself part of the interval.
The high-time counter saturates rather than wraps. high_cycles measures how long SCLK has been away from idle, and after reset it has been away for an unbounded time. Letting it wrap makes it briefly small, which reads as a legal short pulse, and the first frame after reset is flagged. Saturation keeps "a very long time" meaning a very long time.
Every check is edge-referenced, not state-referenced. The monitor has no idea whether a transfer is in progress except by watching chip select, which is exactly the constraint that makes it reusable across implementations.
// spi_master_props.sv
//
// Chapter 13.1 -- the requirements, as logic.
//
// A design requirement that cannot be checked is not a requirement; it is
// an intention. So the deliverable of this chapter is not a document but
// this block: a monitor that watches a master's pins and reports which
// requirement was violated.
//
// Six requirements, derived from earlier modules and stated so that each
// one is independently observable:
//
// R0 SCLK rests at the CPOL level whenever CS is inactive (Ch 3.1)
// R1 no SCLK edge occurs while CS is inactive (Ch 8.6)
// R2 CS leads the first SCLK edge by at least LEAD cycles (Ch 2.5)
// R3 CS lags the last SCLK edge by at least LAG cycles (Ch 2.5)
// R4 a frame contains exactly 2 x BITS SCLK edges (Ch 4.2)
// R5 CS stays inactive at least MIN_HIGH cycles between frames (Ch 7.3)
//
// Each has its own sticky bit, because "the master is wrong" is not
// actionable and "the lag is short" is.
//
// SAME CLOCK DOMAIN. This monitor is clocked by the system clock that also
// generates SCLK, so SCLK and CS are registered signals and edge detection
// is exact -- no synchronisers, and no metastability. That is a deliberate
// difference from Chapter 10.2's observer, which watched a foreign bus and
// had to synchronise everything. A monitor inside the design it checks is
// both simpler and more precise, which is a reason to put it there.
module spi_master_props #(
parameter int CNT_W = 16,
parameter int LEAD = 2, // cycles CS must lead the first edge
parameter int LAG = 2, // cycles CS must lag the last edge
parameter int MIN_HIGH = 4, // cycles CS must stay inactive between frames
parameter int BITS = 8, // bits per frame
parameter bit CPOL = 1'b0
) (
input logic clk,
input logic rst_n,
// The master's own pins, observed from inside the design.
input logic sclk,
input logic cs_n,
input logic mosi,
input logic clear, // restart the observation window
output logic [5:0] viol, // one sticky bit per requirement
output logic [CNT_W-1:0] frames_seen,
output logic [CNT_W-1:0] edges_in_frame,
// The MEASURED intervals of the most recent frame. A monitor that
// reports only a boolean says the lag was short; one that reports the
// value says how short, which is the difference between a bug report
// and a fix. This is Chapter 11.3's argument -- report the damage, not
// just the refusal -- applied to a timing monitor.
output logic [CNT_W-1:0] meas_lead,
output logic [CNT_W-1:0] meas_lag,
output logic [CNT_W-1:0] meas_gap,
// Continuously assigned, so declared as a net: an output driven by an
// assign cannot be a variable in Verilog-2001, and using `wire` here
// keeps the two published sources structurally identical.
output wire ok // no requirement violated yet
);
localparam int V_IDLE = 0; // R0
localparam int V_EDGE = 1; // R1
localparam int V_LEAD = 2; // R2
localparam int V_LAG = 3; // R3
localparam int V_BITS = 4; // R4
localparam int V_GAP = 5; // R5
logic sclk_q, cs_q;
// Every "time since" counter here reloads with ONE, not zero. The cycle
// in which the event is detected is already the first cycle of the
// interval, and a comparison made at a later edge reads the value the
// PREVIOUS edge assigned. Reloading with zero makes every interval
// measure one cycle short, so a master meeting its specification exactly
// is reported as violating it -- which is the worst possible failure for
// a monitor, because the design gets changed to satisfy the bug.
logic [CNT_W-1:0] since_cs_fall; // for the lead check
logic [CNT_W-1:0] since_last_edge; // for the lag check
logic [CNT_W-1:0] high_cycles; // for the inter-frame check
logic seen_edge; // any edge yet this frame
wire sclk_edge = (sclk != sclk_q);
wire cs_active = (cs_n == 1'b0);
wire cs_fall = (cs_q == 1'b1) && (cs_n == 1'b0);
wire cs_rise = (cs_q == 1'b0) && (cs_n == 1'b1);
assign ok = (viol == 6'b000000);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sclk_q <= CPOL;
cs_q <= 1'b1;
since_cs_fall <= {CNT_W{1'b0}};
since_last_edge <= {CNT_W{1'b0}};
// Reset leaves CS having been inactive for a long time, so the
// first frame is not spuriously reported as too soon. A monitor
// that flagged its own first frame would be discarded.
high_cycles <= {CNT_W{1'b1}};
seen_edge <= 1'b0;
viol <= 6'b000000;
frames_seen <= {CNT_W{1'b0}};
edges_in_frame <= {CNT_W{1'b0}};
meas_lead <= {CNT_W{1'b0}};
meas_lag <= {CNT_W{1'b0}};
meas_gap <= {CNT_W{1'b0}};
end else begin
sclk_q <= sclk;
cs_q <= cs_n;
if (clear) begin
viol <= 6'b000000;
frames_seen <= {CNT_W{1'b0}};
edges_in_frame <= {CNT_W{1'b0}};
end
// ---- R0: the idle level -------------------------------------
// Checked continuously while CS is inactive, because a wrong
// idle level is a static fault and not an event.
if (!cs_active && (sclk != CPOL))
viol[V_IDLE] <= 1'b1;
// ---- R1: no edges while inactive ----------------------------
// Distinct from R0: a spurious edge is a glitch, a wrong level
// is a configuration error, and they are found in different
// places.
if (!cs_active && sclk_edge)
viol[V_EDGE] <= 1'b1;
// ---- counters ------------------------------------------------
if (cs_fall) begin
// R5: how long was CS inactive before this frame?
meas_gap <= high_cycles;
if (high_cycles < CNT_W'(MIN_HIGH))
viol[V_GAP] <= 1'b1;
since_cs_fall <= {{(CNT_W-1){1'b0}}, 1'b1};
seen_edge <= 1'b0;
edges_in_frame <= {CNT_W{1'b0}};
high_cycles <= {{(CNT_W-1){1'b0}}, 1'b1};
end else if (cs_active) begin
since_cs_fall <= since_cs_fall + 1'b1;
if (sclk_edge) begin
// R2: the FIRST edge must be at least LEAD cycles after
// CS fell. Only the first -- later edges are the
// divider's business, not the framing's.
if (!seen_edge) begin
meas_lead <= since_cs_fall;
if (since_cs_fall < CNT_W'(LEAD))
viol[V_LEAD] <= 1'b1;
end
seen_edge <= 1'b1;
edges_in_frame <= edges_in_frame + 1'b1;
since_last_edge <= {{(CNT_W-1){1'b0}}, 1'b1};
end else begin
since_last_edge <= since_last_edge + 1'b1;
end
end else begin
// SATURATE rather than wrap. Reset leaves this counter at
// its maximum so the first frame is not reported as too
// soon -- and a counter that wrapped from there would
// report the longest possible idle interval as the
// shortest, flagging a perfectly-spaced first frame.
if (high_cycles != {CNT_W{1'b1}})
high_cycles <= high_cycles + 1'b1;
end
if (cs_rise) begin
frames_seen <= frames_seen + 1'b1;
// R3: CS must have stayed asserted at least LAG cycles past
// the last edge.
if (seen_edge) begin
meas_lag <= since_last_edge;
if (since_last_edge < CNT_W'(LAG))
viol[V_LAG] <= 1'b1;
end
// R4: exactly two edges per bit. A frame with the wrong
// count is the failure signature of every width mismatch in
// this track, so it earns its own bit.
if (edges_in_frame != CNT_W'(2 * BITS))
viol[V_BITS] <= 1'b1;
high_cycles <= {{(CNT_W-1){1'b0}}, 1'b1};
end
end
end
endmodule// spi_master_props_tb.sv
//
// A monitor is only useful if each of its reports means one thing. So every
// requirement is violated INDEPENDENTLY and the testbench requires exactly
// that bit to set and no other -- which is a much stronger claim than "a
// bad master is detected".
//
// The compliant master model is parameterised on the same four intervals
// the monitor checks, so a violation can be injected by changing one number
// rather than by writing a second, deliberately broken, master.
`timescale 1ns/1ps
module spi_master_props_tb;
localparam int CNT_W = 16;
localparam int LEAD = 2;
localparam int LAG = 2;
localparam int MIN_HIGH = 4;
localparam int BITS = 8;
localparam bit CPOL = 1'b0;
localparam int V_IDLE = 0;
localparam int V_EDGE = 1;
localparam int V_LEAD = 2;
localparam int V_LAG = 3;
localparam int V_BITS = 4;
localparam int V_GAP = 5;
logic clk = 1'b0;
logic rst_n = 1'b0;
always #5 clk = ~clk;
logic sclk = CPOL;
logic cs_n = 1'b1;
logic mosi = 1'b0;
logic clear = 1'b0;
// Driven by the DUT, so declared as nets.
wire [5:0] viol;
wire [CNT_W-1:0] frames_seen, edges_in_frame;
wire [CNT_W-1:0] meas_lead, meas_lag, meas_gap;
wire ok;
int errors = 0;
spi_master_props #(
.CNT_W(CNT_W), .LEAD(LEAD), .LAG(LAG), .MIN_HIGH(MIN_HIGH),
.BITS(BITS), .CPOL(CPOL)
) dut (
.clk(clk), .rst_n(rst_n),
.sclk(sclk), .cs_n(cs_n), .mosi(mosi), .clear(clear),
.viol(viol), .frames_seen(frames_seen),
.edges_in_frame(edges_in_frame),
.meas_lead(meas_lead), .meas_lag(meas_lag), .meas_gap(meas_gap),
.ok(ok)
);
// A compliant master, parameterised on the four intervals so that any
// single requirement can be broken by passing a different number.
task automatic frame(input int lead, input int lag, input int bits,
input logic idle_lvl, input logic [7:0] data);
begin
sclk = idle_lvl;
@(negedge clk);
cs_n = 1'b0; // assert
repeat (lead) @(negedge clk); // lead
for (int i = 0; i < bits; i++) begin
mosi = data[7 - (i % 8)];
sclk = ~idle_lvl; // leading edge
@(negedge clk);
sclk = idle_lvl; // trailing edge
@(negedge clk);
end
repeat (lag) @(negedge clk); // lag
cs_n = 1'b1; // release
end
endtask
task automatic gap(input int cycles);
begin
repeat (cycles) @(negedge clk);
end
endtask
task automatic restart;
begin
@(negedge clk); clear = 1'b1;
@(negedge clk); clear = 1'b0;
// Leave a long inactive interval so the next frame's inter-frame
// check is satisfied by construction.
gap(MIN_HIGH + 4);
end
endtask
task automatic expect_only(input string what, input int bitno);
begin
if (!viol[bitno]) begin
$display(" FAIL: %s did not set bit %0d (viol=%06b)",
what, bitno, viol);
errors++;
end
for (int b = 0; b < 6; b++) begin
if (b != bitno && viol[b]) begin
$display(" FAIL: %s also set bit %0d (viol=%06b)",
what, b, viol);
errors++;
end
end
$display(" %-28s viol=%06b -- only bit %0d", what, viol, bitno);
end
endtask
initial begin
repeat (3) @(negedge clk);
rst_n = 1'b1;
restart();
// 1. A COMPLIANT MASTER. Three frames, every interval met.
for (int f = 0; f < 3; f++) begin
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
end
if (!ok) begin
$display(" FAIL: a compliant master reported viol=%06b", viol);
errors++;
end
if (frames_seen !== CNT_W'(3)) begin
$display(" FAIL: %0d frames counted, expected 3", frames_seen);
errors++;
end
$display(" compliant master: 3 frames, viol=%06b, ok=%0b",
viol, ok);
// 2. R0 -- the wrong idle level, ALONE. Note that R0 and R1 are not
// independently reachable by a transition: any arrival at the
// wrong level is itself an edge, so setting SCLK high here would
// legitimately set both bits.
//
// R0 alone is reachable only when the line is ALREADY wrong as
// observation begins -- which is exactly what a mis-set CPOL
// looks like, since it is a persistent state rather than an
// event. So hold the line wrong, clear the window, and keep
// holding it.
restart();
sclk = ~CPOL;
gap(4);
@(negedge clk); clear = 1'b1;
@(negedge clk); clear = 1'b0;
gap(4); // still wrong, but no NEW edge
expect_only("R0: wrong idle level", V_IDLE);
sclk = CPOL;
// 3. R1 -- a spurious edge while CS is inactive. Distinct from R0:
// a glitch rather than a configuration error.
restart();
@(negedge clk); sclk = ~CPOL;
@(negedge clk); sclk = CPOL; // one full glitch, idle level restored
@(negedge clk);
if (!viol[V_EDGE]) begin
$display(" FAIL: a glitch while CS was inactive was not reported");
errors++;
end
// The glitch necessarily spends a cycle at the wrong level, so R0
// fires too -- and that is CORRECT rather than a false positive: a
// glitch does put the line at the wrong level. The testbench states
// the expectation rather than suppressing it.
$display(" R1: glitch while inactive viol=%06b -- edge and level both, correctly",
viol);
if (viol[V_LEAD] || viol[V_LAG] || viol[V_BITS] || viol[V_GAP]) begin
$display(" FAIL: a glitch set a framing bit"); errors++;
end
// 4. R2 -- the lead too short. One cycle where two are required.
restart();
frame(LEAD - 1, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
expect_only("R2: lead one cycle short", V_LEAD);
// 5. R3 -- the lag too short. Asserted against the MEASURED lag
// rather than against the stimulus parameter: the frame task's
// own trailing wait contributes a cycle, so the parameter and the
// observed interval differ by one. Checking the measurement makes
// the test independent of that, and is why the monitor reports it.
restart();
frame(LEAD, LAG - 2, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (meas_lag >= CNT_W'(LAG)) begin
$display(" FAIL: the stimulus produced a lag of %0d, which is not short",
meas_lag);
errors++;
end
expect_only("R3: lag short", V_LAG);
$display(" measured lag = %0d, required %0d", meas_lag, LAG);
// 6. R4 -- the wrong bit count, both directions. Seven bits and
// nine, because a width error can go either way and a monitor
// that only caught one would miss half of them.
restart();
frame(LEAD, LAG, BITS - 1, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
expect_only("R4: seven bits", V_BITS);
restart();
frame(LEAD, LAG, BITS + 1, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
expect_only("R4: nine bits", V_BITS);
// 7. R5 -- the inter-frame gap too short, again asserted against
// the measurement.
restart();
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH - 3);
frame(LEAD, LAG, BITS, CPOL, 8'h5A);
gap(MIN_HIGH + 2);
if (meas_gap >= CNT_W'(MIN_HIGH)) begin
$display(" FAIL: the stimulus produced a gap of %0d, which is not short",
meas_gap);
errors++;
end
expect_only("R5: gap short", V_GAP);
$display(" measured gap = %0d, required %0d", meas_gap, MIN_HIGH);
// 8. THE EXACT BOUNDARIES. Each interval at its minimum must PASS,
// and one less must fail -- the pair that a comparison written
// with the wrong relational operator gets wrong in exactly one
// direction.
// The stimulus parameters are chosen so the MEASURED intervals land
// exactly on their minimums, which the assertions below confirm --
// otherwise this would test a comfortable case and claim to test a
// boundary.
restart();
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH - 1);
frame(LEAD, LAG - 1, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (meas_lead !== CNT_W'(LEAD) || meas_lag !== CNT_W'(LAG) ||
meas_gap !== CNT_W'(MIN_HIGH)) begin
$display(" FAIL: the boundary stimulus measured lead=%0d lag=%0d gap=%0d, wanted %0d/%0d/%0d",
meas_lead, meas_lag, meas_gap, LEAD, LAG, MIN_HIGH);
errors++;
end
if (!ok) begin
$display(" FAIL: every interval at exactly its minimum was rejected (viol=%06b, lead=%0d lag=%0d gap=%0d)",
viol, meas_lead, meas_lag, meas_gap);
errors++;
end
$display(" intervals at minimum: lead=%0d lag=%0d gap=%0d, viol=%06b -- accepted",
meas_lead, meas_lag, meas_gap, viol);
// 9. SEVERAL VIOLATIONS AT ONCE. Software fixes the whole master in
// one pass rather than discovering one fault per attempt --
// the same argument Chapter 10.1 made for its profile validator.
restart();
frame(LEAD - 1, LAG - 2, BITS - 1, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (!(viol[V_LEAD] && viol[V_LAG] && viol[V_BITS])) begin
$display(" FAIL: three simultaneous violations gave viol=%06b", viol);
errors++;
end
$display(" lead+lag+bits all short: viol=%06b -- three bits together",
viol);
// 10. CLEAR restarts the window, so a fixed master is not judged by
// a previous one's failures.
restart();
if (!ok || frames_seen !== CNT_W'(0)) begin
$display(" FAIL: clear did not restart the window (viol=%06b frames=%0d)",
viol, frames_seen);
errors++;
end
for (int f = 0; f < 4; f++) begin
frame(LEAD, LAG, BITS, CPOL, 8'h3C);
gap(MIN_HIGH + 2);
end
if (!ok) begin
$display(" FAIL: a compliant master after violations reported viol=%06b",
viol);
errors++;
end
$display(" after clear: 4 clean frames, viol=%06b, %0d frames counted",
viol, frames_seen);
// 11. THE FIRST FRAME AFTER RESET must not be reported as too soon.
// A monitor that flagged its own first frame would be removed
// from the design, so reset must leave the gap counter high.
@(negedge clk); rst_n = 1'b0;
@(negedge clk); rst_n = 1'b1;
@(negedge clk);
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (viol[V_GAP]) begin
$display(" FAIL: the first frame after reset was reported as too soon");
errors++;
end
$display(" first frame after reset: viol=%06b -- not flagged", viol);
if (errors == 0)
$display("PASS: a compliant master reports no violation and is counted correctly, each of the six requirements is violated independently and sets exactly its own bit, a glitch correctly reports both an edge and a level fault without touching any framing bit, every interval is accepted at exactly its minimum and rejected one cycle below it, several violations are reported together, clear restarts the window, and the first frame after reset is not flagged as too soon");
else
$display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule// spi_master_props.v
//
// Chapter 13.1 -- the requirements, as logic.
//
// A design requirement that cannot be checked is not a requirement; it is
// an intention. So the deliverable of this chapter is not a document but
// this block: a monitor that watches a master's pins and reports which
// requirement was violated.
//
// Six requirements, derived from earlier modules and stated so that each
// one is independently observable:
//
// R0 SCLK rests at the CPOL level whenever CS is inactive (Ch 3.1)
// R1 no SCLK edge occurs while CS is inactive (Ch 8.6)
// R2 CS leads the first SCLK edge by at least LEAD cycles (Ch 2.5)
// R3 CS lags the last SCLK edge by at least LAG cycles (Ch 2.5)
// R4 a frame contains exactly 2 x BITS SCLK edges (Ch 4.2)
// R5 CS stays inactive at least MIN_HIGH cycles between frames (Ch 7.3)
//
// Each has its own sticky bit, because "the master is wrong" is not
// actionable and "the lag is short" is.
//
// SAME CLOCK DOMAIN. This monitor is clocked by the system clock that also
// generates SCLK, so SCLK and CS are registered signals and edge detection
// is exact -- no synchronisers, and no metastability. That is a deliberate
// difference from Chapter 10.2's observer, which watched a foreign bus and
// had to synchronise everything. A monitor inside the design it checks is
// both simpler and more precise, which is a reason to put it there.
module spi_master_props #(
parameter CNT_W = 16,
parameter LEAD = 2, // cycles CS must lead the first edge
parameter LAG = 2, // cycles CS must lag the last edge
parameter MIN_HIGH = 4, // cycles CS must stay inactive between frames
parameter BITS = 8, // bits per frame
parameter CPOL = 1'b0
) (
input wire clk,
input wire rst_n,
// The master's own pins, observed from inside the design.
input wire sclk,
input wire cs_n,
input wire mosi,
input wire clear, // restart the observation window
output reg [5:0] viol, // one sticky bit per requirement
output reg [CNT_W-1:0] frames_seen,
output reg [CNT_W-1:0] edges_in_frame,
// The MEASURED intervals of the most recent frame. A monitor that
// reports only a boolean says the lag was short; one that reports the
// value says how short, which is the difference between a bug report
// and a fix. This is Chapter 11.3's argument -- report the damage, not
// just the refusal -- applied to a timing monitor.
output reg [CNT_W-1:0] meas_lead,
output reg [CNT_W-1:0] meas_lag,
output reg [CNT_W-1:0] meas_gap,
// Continuously assigned, so declared as a net: an output driven by an
// assign cannot be a variable in Verilog-2001, and using `wire` here
// keeps the two published sources structurally identical.
output wire ok // no requirement violated yet
);
localparam V_IDLE = 0; // R0
localparam V_EDGE = 1; // R1
localparam V_LEAD = 2; // R2
localparam V_LAG = 3; // R3
localparam V_BITS = 4; // R4
localparam V_GAP = 5; // R5
reg sclk_q, cs_q;
// Every "time since" counter here reloads with ONE, not zero. The cycle
// in which the event is detected is already the first cycle of the
// interval, and a comparison made at a later edge reads the value the
// PREVIOUS edge assigned. Reloading with zero makes every interval
// measure one cycle short, so a master meeting its specification exactly
// is reported as violating it -- which is the worst possible failure for
// a monitor, because the design gets changed to satisfy the bug.
reg [CNT_W-1:0] since_cs_fall; // for the lead check
reg [CNT_W-1:0] since_last_edge; // for the lag check
reg [CNT_W-1:0] high_cycles; // for the inter-frame check
reg seen_edge; // any edge yet this frame
wire sclk_edge = (sclk != sclk_q);
wire cs_active = (cs_n == 1'b0);
wire cs_fall = (cs_q == 1'b1) && (cs_n == 1'b0);
wire cs_rise = (cs_q == 1'b0) && (cs_n == 1'b1);
assign ok = (viol == 6'b000000);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sclk_q <= CPOL;
cs_q <= 1'b1;
since_cs_fall <= {CNT_W{1'b0}};
since_last_edge <= {CNT_W{1'b0}};
// Reset leaves CS having been inactive for a long time, so the
// first frame is not spuriously reported as too soon. A monitor
// that flagged its own first frame would be discarded.
high_cycles <= {CNT_W{1'b1}};
seen_edge <= 1'b0;
viol <= 6'b000000;
frames_seen <= {CNT_W{1'b0}};
edges_in_frame <= {CNT_W{1'b0}};
meas_lead <= {CNT_W{1'b0}};
meas_lag <= {CNT_W{1'b0}};
meas_gap <= {CNT_W{1'b0}};
end else begin
sclk_q <= sclk;
cs_q <= cs_n;
if (clear) begin
viol <= 6'b000000;
frames_seen <= {CNT_W{1'b0}};
edges_in_frame <= {CNT_W{1'b0}};
end
// ---- R0: the idle level -------------------------------------
// Checked continuously while CS is inactive, because a wrong
// idle level is a static fault and not an event.
if (!cs_active && (sclk != CPOL))
viol[V_IDLE] <= 1'b1;
// ---- R1: no edges while inactive ----------------------------
// Distinct from R0: a spurious edge is a glitch, a wrong level
// is a configuration error, and they are found in different
// places.
if (!cs_active && sclk_edge)
viol[V_EDGE] <= 1'b1;
// ---- counters ------------------------------------------------
if (cs_fall) begin
// R5: how long was CS inactive before this frame?
meas_gap <= high_cycles;
if (high_cycles < (MIN_HIGH))
viol[V_GAP] <= 1'b1;
since_cs_fall <= {{(CNT_W-1){1'b0}}, 1'b1};
seen_edge <= 1'b0;
edges_in_frame <= {CNT_W{1'b0}};
high_cycles <= {{(CNT_W-1){1'b0}}, 1'b1};
end else if (cs_active) begin
since_cs_fall <= since_cs_fall + 1'b1;
if (sclk_edge) begin
// R2: the FIRST edge must be at least LEAD cycles after
// CS fell. Only the first -- later edges are the
// divider's business, not the framing's.
if (!seen_edge) begin
meas_lead <= since_cs_fall;
if (since_cs_fall < (LEAD))
viol[V_LEAD] <= 1'b1;
end
seen_edge <= 1'b1;
edges_in_frame <= edges_in_frame + 1'b1;
since_last_edge <= {{(CNT_W-1){1'b0}}, 1'b1};
end else begin
since_last_edge <= since_last_edge + 1'b1;
end
end else begin
// SATURATE rather than wrap. Reset leaves this counter at
// its maximum so the first frame is not reported as too
// soon -- and a counter that wrapped from there would
// report the longest possible idle interval as the
// shortest, flagging a perfectly-spaced first frame.
if (high_cycles != {CNT_W{1'b1}})
high_cycles <= high_cycles + 1'b1;
end
if (cs_rise) begin
frames_seen <= frames_seen + 1'b1;
// R3: CS must have stayed asserted at least LAG cycles past
// the last edge.
if (seen_edge) begin
meas_lag <= since_last_edge;
if (since_last_edge < (LAG))
viol[V_LAG] <= 1'b1;
end
// R4: exactly two edges per bit. A frame with the wrong
// count is the failure signature of every width mismatch in
// this track, so it earns its own bit.
if (edges_in_frame != (2 * BITS))
viol[V_BITS] <= 1'b1;
high_cycles <= {{(CNT_W-1){1'b0}}, 1'b1};
end
end
end
endmodule// spi_master_props_tb.v
//
// A monitor is only useful if each of its reports means one thing. So every
// requirement is violated INDEPENDENTLY and the testbench requires exactly
// that bit to set and no other -- which is a much stronger claim than "a
// bad master is detected".
//
// The compliant master model is parameterised on the same four intervals
// the monitor checks, so a violation can be injected by changing one number
// rather than by writing a second, deliberately broken, master.
`timescale 1ns/1ps
module spi_master_props_tb;
integer b;
integer f;
integer i;
localparam CNT_W = 16;
localparam LEAD = 2;
localparam LAG = 2;
localparam MIN_HIGH = 4;
localparam BITS = 8;
localparam CPOL = 1'b0;
localparam V_IDLE = 0;
localparam V_EDGE = 1;
localparam V_LEAD = 2;
localparam V_LAG = 3;
localparam V_BITS = 4;
localparam V_GAP = 5;
reg clk;
reg rst_n;
always #5 clk = ~clk;
reg sclk;
reg cs_n;
reg mosi;
reg clear;
// Driven by the DUT, so declared as nets.
wire [5:0] viol;
wire [CNT_W-1:0] frames_seen, edges_in_frame;
wire [CNT_W-1:0] meas_lead, meas_lag, meas_gap;
wire ok;
integer errors;
spi_master_props #(
.CNT_W(CNT_W), .LEAD(LEAD), .LAG(LAG), .MIN_HIGH(MIN_HIGH),
.BITS(BITS), .CPOL(CPOL)
) dut (
.clk(clk), .rst_n(rst_n),
.sclk(sclk), .cs_n(cs_n), .mosi(mosi), .clear(clear),
.viol(viol), .frames_seen(frames_seen),
.edges_in_frame(edges_in_frame),
.meas_lead(meas_lead), .meas_lag(meas_lag), .meas_gap(meas_gap),
.ok(ok)
);
// A compliant master, parameterised on the four intervals so that any
// single requirement can be broken by passing a different number.
task frame;
input integer lead;
input integer lag;
input integer bits;
input idle_lvl;
input [7:0] data;
begin
sclk = idle_lvl;
@(negedge clk);
cs_n = 1'b0; // assert
repeat (lead) @(negedge clk); // lead
for (i = 0; i < bits; i = i + 1) begin
mosi = data[7 - (i % 8)];
sclk = ~idle_lvl; // leading edge
@(negedge clk);
sclk = idle_lvl; // trailing edge
@(negedge clk);
end
repeat (lag) @(negedge clk); // lag
cs_n = 1'b1; // release
end
endtask
task gap;
input integer cycles;
begin
repeat (cycles) @(negedge clk);
end
endtask
task restart;
begin
@(negedge clk); clear = 1'b1;
@(negedge clk); clear = 1'b0;
// Leave a long inactive interval so the next frame's inter-frame
// check is satisfied by construction.
gap(MIN_HIGH + 4);
end
endtask
task expect_only;
input [8*40:1] what;
input integer bitno;
begin
if (!viol[bitno]) begin
$display(" FAIL: %0s did not set bit %0d (viol=%06b)",
what, bitno, viol);
errors = errors + 1;
end
for (b = 0; b < 6; b = b + 1) begin
if (b != bitno && viol[b]) begin
$display(" FAIL: %0s also set bit %0d (viol=%06b)",
what, b, viol);
errors = errors + 1;
end
end
$display(" %0s viol=%06b -- only bit %0d", what, viol, bitno);
end
endtask
initial begin
repeat (3) @(negedge clk);
rst_n = 1'b1;
restart();
// 1. A COMPLIANT MASTER. Three frames, every interval met.
for (f = 0; f < 3; f = f + 1) begin
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
end
if (!ok) begin
$display(" FAIL: a compliant master reported viol=%06b", viol);
errors = errors + 1;
end
if (frames_seen !== (3)) begin
$display(" FAIL: %0d frames counted, expected 3", frames_seen);
errors = errors + 1;
end
$display(" compliant master: 3 frames, viol=%06b, ok=%0b",
viol, ok);
// 2. R0 -- the wrong idle level, ALONE. Note that R0 and R1 are not
// independently reachable by a transition: any arrival at the
// wrong level is itself an edge, so setting SCLK high here would
// legitimately set both bits.
//
// R0 alone is reachable only when the line is ALREADY wrong as
// observation begins -- which is exactly what a mis-set CPOL
// looks like, since it is a persistent state rather than an
// event. So hold the line wrong, clear the window, and keep
// holding it.
restart();
sclk = ~CPOL;
gap(4);
@(negedge clk); clear = 1'b1;
@(negedge clk); clear = 1'b0;
gap(4); // still wrong, but no NEW edge
expect_only("R0: wrong idle level", V_IDLE);
sclk = CPOL;
// 3. R1 -- a spurious edge while CS is inactive. Distinct from R0:
// a glitch rather than a configuration error.
restart();
@(negedge clk); sclk = ~CPOL;
@(negedge clk); sclk = CPOL; // one full glitch, idle level restored
@(negedge clk);
if (!viol[V_EDGE]) begin
$display(" FAIL: a glitch while CS was inactive was not reported");
errors = errors + 1;
end
// The glitch necessarily spends a cycle at the wrong level, so R0
// fires too -- and that is CORRECT rather than a false positive: a
// glitch does put the line at the wrong level. The testbench states
// the expectation rather than suppressing it.
$display(" R1: glitch while inactive viol=%06b -- edge and level both, correctly",
viol);
if (viol[V_LEAD] || viol[V_LAG] || viol[V_BITS] || viol[V_GAP]) begin
$display(" FAIL: a glitch set a framing bit"); errors = errors + 1;
end
// 4. R2 -- the lead too short. One cycle where two are required.
restart();
frame(LEAD - 1, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
expect_only("R2: lead one cycle short", V_LEAD);
// 5. R3 -- the lag too short. Asserted against the MEASURED lag
// rather than against the stimulus parameter: the frame task's
// own trailing wait contributes a cycle, so the parameter and the
// observed interval differ by one. Checking the measurement makes
// the test independent of that, and is why the monitor reports it.
restart();
frame(LEAD, LAG - 2, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (meas_lag >= (LAG)) begin
$display(" FAIL: the stimulus produced a lag of %0d, which is not short",
meas_lag);
errors = errors + 1;
end
expect_only("R3: lag short", V_LAG);
$display(" measured lag = %0d, required %0d", meas_lag, LAG);
// 6. R4 -- the wrong bit count, both directions. Seven bits and
// nine, because a width error can go either way and a monitor
// that only caught one would miss half of them.
restart();
frame(LEAD, LAG, BITS - 1, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
expect_only("R4: seven bits", V_BITS);
restart();
frame(LEAD, LAG, BITS + 1, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
expect_only("R4: nine bits", V_BITS);
// 7. R5 -- the inter-frame gap too short, again asserted against
// the measurement.
restart();
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH - 3);
frame(LEAD, LAG, BITS, CPOL, 8'h5A);
gap(MIN_HIGH + 2);
if (meas_gap >= (MIN_HIGH)) begin
$display(" FAIL: the stimulus produced a gap of %0d, which is not short",
meas_gap);
errors = errors + 1;
end
expect_only("R5: gap short", V_GAP);
$display(" measured gap = %0d, required %0d", meas_gap, MIN_HIGH);
// 8. THE EXACT BOUNDARIES. Each interval at its minimum must PASS,
// and one less must fail -- the pair that a comparison written
// with the wrong relational operator gets wrong in exactly one
// direction.
// The stimulus parameters are chosen so the MEASURED intervals land
// exactly on their minimums, which the assertions below confirm --
// otherwise this would test a comfortable case and claim to test a
// boundary.
restart();
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH - 1);
frame(LEAD, LAG - 1, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (meas_lead !== (LEAD) || meas_lag !== (LAG) ||
meas_gap !== (MIN_HIGH)) begin
$display(" FAIL: the boundary stimulus measured lead=%0d lag=%0d gap=%0d, wanted %0d/%0d/%0d",
meas_lead, meas_lag, meas_gap, LEAD, LAG, MIN_HIGH);
errors = errors + 1;
end
if (!ok) begin
$display(" FAIL: every interval at exactly its minimum was rejected (viol=%06b, lead=%0d lag=%0d gap=%0d)",
viol, meas_lead, meas_lag, meas_gap);
errors = errors + 1;
end
$display(" intervals at minimum: lead=%0d lag=%0d gap=%0d, viol=%06b -- accepted",
meas_lead, meas_lag, meas_gap, viol);
// 9. SEVERAL VIOLATIONS AT ONCE. Software fixes the whole master in
// one pass rather than discovering one fault per attempt --
// the same argument Chapter 10.1 made for its profile validator.
restart();
frame(LEAD - 1, LAG - 2, BITS - 1, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (!(viol[V_LEAD] && viol[V_LAG] && viol[V_BITS])) begin
$display(" FAIL: three simultaneous violations gave viol=%06b", viol);
errors = errors + 1;
end
$display(" lead+lag+bits all short: viol=%06b -- three bits together",
viol);
// 10. CLEAR restarts the window, so a fixed master is not judged by
// a previous one's failures.
restart();
if (!ok || frames_seen !== (0)) begin
$display(" FAIL: clear did not restart the window (viol=%06b frames=%0d)",
viol, frames_seen);
errors = errors + 1;
end
for (f = 0; f < 4; f = f + 1) begin
frame(LEAD, LAG, BITS, CPOL, 8'h3C);
gap(MIN_HIGH + 2);
end
if (!ok) begin
$display(" FAIL: a compliant master after violations reported viol=%06b",
viol);
errors = errors + 1;
end
$display(" after clear: 4 clean frames, viol=%06b, %0d frames counted",
viol, frames_seen);
// 11. THE FIRST FRAME AFTER RESET must not be reported as too soon.
// A monitor that flagged its own first frame would be removed
// from the design, so reset must leave the gap counter high.
@(negedge clk); rst_n = 1'b0;
@(negedge clk); rst_n = 1'b1;
@(negedge clk);
frame(LEAD, LAG, BITS, CPOL, 8'hA5);
gap(MIN_HIGH + 2);
if (viol[V_GAP]) begin
$display(" FAIL: the first frame after reset was reported as too soon");
errors = errors + 1;
end
$display(" first frame after reset: viol=%06b -- not flagged", viol);
if (errors == 0)
$display("PASS: a compliant master reports no violation and is counted correctly, each of the six requirements is violated independently and sets exactly its own bit, a glitch correctly reports both an edge and a level fault without touching any framing bit, every interval is accepted at exactly its minimum and rejected one cycle below it, several violations are reported together, clear restarts the window, and the first frame after reset is not flagged as too soon");
else
$display("FAIL: %0d error(s)", errors);
$finish;
end
initial begin
clk = 1'b0;
rst_n = 1'b0;
sclk = CPOL;
cs_n = 1'b1;
mosi = 1'b0;
clear = 1'b0;
errors = 0;
end
endmodule-- spi_master_props.vhd
--
-- Chapter 13.1 -- the requirements, as logic, in VHDL.
--
-- A design requirement that cannot be checked is not a requirement; it is an
-- intention. So the deliverable of this chapter is not a document but this
-- block: a monitor that watches a master's pins and reports WHICH
-- requirement was violated.
--
-- R0 SCLK rests at the CPOL level whenever CS is inactive (Ch 3.1)
-- R1 no SCLK edge occurs while CS is inactive (Ch 8.6)
-- R2 CS leads the first SCLK edge by at least LEAD cycles (Ch 2.5)
-- R3 CS lags the last SCLK edge by at least LAG cycles (Ch 2.5)
-- R4 a frame contains exactly 2 x BITS SCLK edges (Ch 4.2)
-- R5 CS stays inactive at least MIN_HIGH cycles between frames (Ch 7.3)
--
-- Each has its own sticky bit, because "the master is wrong" is not
-- actionable and "the lag is short" is. The measured intervals are reported
-- alongside, because a value is a fix and a boolean is a bug report.
--
-- SAME CLOCK DOMAIN. This monitor is clocked by the system clock that also
-- generates SCLK, so edge detection is exact -- no synchronisers, and no
-- metastability. A monitor inside the design it checks is both simpler and
-- more precise than one watching a foreign bus.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity spi_master_props is
generic (
CNT_W : positive := 16;
LEAD : natural := 2; -- cycles CS must lead the first edge
LAG : natural := 2; -- cycles CS must lag the last edge
MIN_HIGH : natural := 4; -- cycles CS must stay inactive between frames
BITS : positive := 8; -- bits per frame
CPOL : std_logic := '0'
);
port (
clk : in std_logic;
rst_n : in std_logic;
-- The master's own pins, observed from inside the design.
sclk : in std_logic;
cs_n : in std_logic;
mosi : in std_logic;
clear : in std_logic; -- restart the observation window
viol : out std_logic_vector(5 downto 0);
frames_seen : out unsigned(CNT_W - 1 downto 0);
edges_in_frame : out unsigned(CNT_W - 1 downto 0);
-- The MEASURED intervals of the most recent frame.
meas_lead : out unsigned(CNT_W - 1 downto 0);
meas_lag : out unsigned(CNT_W - 1 downto 0);
meas_gap : out unsigned(CNT_W - 1 downto 0);
ok : out std_logic
);
end entity;
architecture rtl of spi_master_props is
constant V_IDLE : natural := 0; -- R0
constant V_EDGE : natural := 1; -- R1
constant V_LEAD : natural := 2; -- R2
constant V_LAG : natural := 3; -- R3
constant V_BITS : natural := 4; -- R4
constant V_GAP : natural := 5; -- R5
signal sclk_q : std_logic := CPOL;
signal cs_q : std_logic := '1';
-- Every "time since" counter here reloads with ONE, not zero. The cycle
-- in which the event is detected is already the first cycle of the
-- interval, and a comparison made at a later edge reads the value the
-- PREVIOUS edge assigned. Reloading with zero makes every interval
-- measure one cycle short, so a master meeting its specification exactly
-- is reported as violating it -- the worst failure for a monitor,
-- because the design gets changed to satisfy the bug.
signal since_cs_fall : unsigned(CNT_W - 1 downto 0) := (others => '0');
signal since_last_edge : unsigned(CNT_W - 1 downto 0) := (others => '0');
signal high_cycles : unsigned(CNT_W - 1 downto 0) := (others => '1');
signal seen_edge : std_logic := '0';
signal viol_r : std_logic_vector(5 downto 0) := (others => '0');
signal frames_r : unsigned(CNT_W - 1 downto 0) := (others => '0');
signal edges_r : unsigned(CNT_W - 1 downto 0) := (others => '0');
signal mlead_r : unsigned(CNT_W - 1 downto 0) := (others => '0');
signal mlag_r : unsigned(CNT_W - 1 downto 0) := (others => '0');
signal mgap_r : unsigned(CNT_W - 1 downto 0) := (others => '0');
signal sclk_edge : std_logic;
signal cs_active : std_logic;
signal cs_fall : std_logic;
signal cs_rise : std_logic;
begin
sclk_edge <= '1' when sclk /= sclk_q else '0';
cs_active <= '1' when cs_n = '0' else '0';
cs_fall <= '1' when cs_q = '1' and cs_n = '0' else '0';
cs_rise <= '1' when cs_q = '0' and cs_n = '1' else '0';
ok <= '1' when viol_r = "000000" else '0';
viol <= viol_r;
frames_seen <= frames_r;
edges_in_frame <= edges_r;
meas_lead <= mlead_r;
meas_lag <= mlag_r;
meas_gap <= mgap_r;
watch : process (clk, rst_n)
begin
if rst_n = '0' then
sclk_q <= CPOL;
cs_q <= '1';
since_cs_fall <= (others => '0');
since_last_edge <= (others => '0');
-- Reset leaves CS having been inactive for a long time, so the
-- first frame is not spuriously reported as too soon.
high_cycles <= (others => '1');
seen_edge <= '0';
viol_r <= (others => '0');
frames_r <= (others => '0');
edges_r <= (others => '0');
mlead_r <= (others => '0');
mlag_r <= (others => '0');
mgap_r <= (others => '0');
elsif rising_edge(clk) then
sclk_q <= sclk;
cs_q <= cs_n;
if clear = '1' then
viol_r <= (others => '0');
frames_r <= (others => '0');
edges_r <= (others => '0');
end if;
-- ---- R0: the idle level ---------------------------------------
-- Checked continuously while CS is inactive, because a wrong idle
-- level is a static fault and not an event.
if cs_active = '0' and sclk /= CPOL then
viol_r(V_IDLE) <= '1';
end if;
-- ---- R1: no edges while inactive ------------------------------
-- Distinct from R0: a spurious edge is a glitch, a wrong level is
-- a configuration error, and they are found in different places.
if cs_active = '0' and sclk_edge = '1' then
viol_r(V_EDGE) <= '1';
end if;
-- ---- counters --------------------------------------------------
if cs_fall = '1' then
-- R5: how long was CS inactive before this frame?
mgap_r <= high_cycles;
if to_integer(high_cycles) < MIN_HIGH then
viol_r(V_GAP) <= '1';
end if;
since_cs_fall <= to_unsigned(1, CNT_W);
seen_edge <= '0';
edges_r <= (others => '0');
high_cycles <= to_unsigned(1, CNT_W);
elsif cs_active = '1' then
since_cs_fall <= since_cs_fall + 1;
if sclk_edge = '1' then
-- R2: the FIRST edge must be at least LEAD cycles after
-- CS fell. Only the first -- later edges are the
-- divider's business, not the framing's.
if seen_edge = '0' then
mlead_r <= since_cs_fall;
if to_integer(since_cs_fall) < LEAD then
viol_r(V_LEAD) <= '1';
end if;
end if;
seen_edge <= '1';
edges_r <= edges_r + 1;
since_last_edge <= to_unsigned(1, CNT_W);
else
since_last_edge <= since_last_edge + 1;
end if;
else
-- SATURATE rather than wrap. Reset leaves this counter at its
-- maximum so the first frame is not reported as too soon, and
-- a counter that wrapped from there would report the longest
-- possible idle interval as the shortest.
if high_cycles /= (high_cycles'range => '1') then
high_cycles <= high_cycles + 1;
end if;
end if;
if cs_rise = '1' then
frames_r <= frames_r + 1;
-- R3: CS must have stayed asserted at least LAG cycles past
-- the last edge.
if seen_edge = '1' then
mlag_r <= since_last_edge;
if to_integer(since_last_edge) < LAG then
viol_r(V_LAG) <= '1';
end if;
end if;
-- R4: exactly two edges per bit. A frame with the wrong count
-- is the failure signature of every width mismatch in this
-- track, so it earns its own bit.
if to_integer(edges_r) /= 2 * BITS then
viol_r(V_BITS) <= '1';
end if;
high_cycles <= to_unsigned(1, CNT_W);
end if;
end if;
end process;
end architecture;-- spi_master_props_tb.vhd
--
-- A monitor is only useful if each of its reports means one thing. So every
-- requirement is violated INDEPENDENTLY and the testbench requires exactly
-- that bit to set and no other.
--
-- The flags are asserted against the MEASURED intervals rather than against
-- the stimulus parameters, because the frame procedure's own trailing wait
-- contributes a cycle -- and checking the measurement makes the test
-- independent of that offset.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity spi_master_props_tb is
end entity;
architecture sim of spi_master_props_tb is
constant CNT_W : positive := 16;
constant LEAD : natural := 2;
constant LAG : natural := 2;
constant MIN_HIGH : natural := 4;
constant BITS : positive := 8;
constant CPOL : std_logic := '0';
constant V_IDLE : natural := 0;
constant V_EDGE : natural := 1;
constant V_LEAD : natural := 2;
constant V_LAG : natural := 3;
constant V_BITS : natural := 4;
constant V_GAP : natural := 5;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal halt : boolean := false;
signal sclk : std_logic := CPOL;
signal cs_n : std_logic := '1';
signal mosi : std_logic := '0';
signal clear : std_logic := '0';
signal viol : std_logic_vector(5 downto 0);
signal frames_seen : unsigned(CNT_W - 1 downto 0);
signal edges_in_frame : unsigned(CNT_W - 1 downto 0);
signal meas_lead : unsigned(CNT_W - 1 downto 0);
signal meas_lag : unsigned(CNT_W - 1 downto 0);
signal meas_gap : unsigned(CNT_W - 1 downto 0);
signal ok : std_logic;
signal errors : natural := 0;
begin
clk <= not clk after 5 ns when not halt else '0';
dut : entity work.spi_master_props
generic map (CNT_W => CNT_W, LEAD => LEAD, LAG => LAG,
MIN_HIGH => MIN_HIGH, BITS => BITS, CPOL => CPOL)
port map (
clk => clk, rst_n => rst_n,
sclk => sclk, cs_n => cs_n, mosi => mosi, clear => clear,
viol => viol, frames_seen => frames_seen,
edges_in_frame => edges_in_frame,
meas_lead => meas_lead, meas_lag => meas_lag, meas_gap => meas_gap,
ok => ok
);
stim : process
variable errs : natural := 0;
-- A compliant master, parameterised on the intervals so that any
-- single requirement can be broken by passing a different number.
-- The parameters are NOT named after the generics: VHDL is
-- case-insensitive, so a parameter called `lead` would be the same
-- identifier as the generic LEAD and would shadow it.
procedure frame(n_lead : natural; n_lag : natural; n_bits : natural;
idle_lvl : std_logic;
data : std_logic_vector(7 downto 0)) is
begin
sclk <= idle_lvl;
wait until falling_edge(clk);
cs_n <= '0'; -- assert
for k in 1 to n_lead loop -- lead
wait until falling_edge(clk);
end loop;
for i in 0 to n_bits - 1 loop
mosi <= data(7 - (i mod 8));
sclk <= not idle_lvl; -- leading edge
wait until falling_edge(clk);
sclk <= idle_lvl; -- trailing edge
wait until falling_edge(clk);
end loop;
for k in 1 to n_lag loop -- lag
wait until falling_edge(clk);
end loop;
cs_n <= '1'; -- release
end procedure;
procedure gap(cycles : natural) is
begin
for k in 1 to cycles loop
wait until falling_edge(clk);
end loop;
end procedure;
procedure restart is
begin
wait until falling_edge(clk);
clear <= '1';
wait until falling_edge(clk);
clear <= '0';
gap(MIN_HIGH + 4);
end procedure;
procedure expect_only(what : string; bitno : natural) is
begin
if viol(bitno) /= '1' then
report " FAIL: " & what & " did not set bit " &
integer'image(bitno);
errs := errs + 1;
end if;
for b in 0 to 5 loop
if b /= bitno and viol(b) = '1' then
report " FAIL: " & what & " also set bit " &
integer'image(b);
errs := errs + 1;
end if;
end loop;
report " " & what & " -- only bit " & integer'image(bitno);
end procedure;
begin
for k in 0 to 2 loop
wait until falling_edge(clk);
end loop;
rst_n <= '1';
restart;
-- 1. A COMPLIANT MASTER. Three frames, every interval met.
for f in 0 to 2 loop
frame(LEAD, LAG, BITS, CPOL, x"A5");
gap(MIN_HIGH + 2);
end loop;
if ok /= '1' then
report " FAIL: a compliant master reported a violation";
errs := errs + 1;
end if;
if to_integer(frames_seen) /= 3 then
report " FAIL: the frame count is wrong"; errs := errs + 1;
end if;
report " compliant master: 3 frames, no violation";
-- 2. R0 -- the wrong idle level, ALONE. R0 and R1 are not
-- independently reachable by a transition: any arrival at the
-- wrong level is itself an edge. R0 alone is reachable only when
-- the line is ALREADY wrong as observation begins -- which is what
-- a mis-set CPOL looks like, being a persistent state rather than
-- an event.
restart;
sclk <= not CPOL;
gap(4);
wait until falling_edge(clk);
clear <= '1';
wait until falling_edge(clk);
clear <= '0';
gap(4); -- still wrong, but no NEW edge
expect_only("R0: wrong idle level", V_IDLE);
sclk <= CPOL;
-- 3. R1 -- a spurious edge while CS is inactive.
restart;
wait until falling_edge(clk);
sclk <= not CPOL;
wait until falling_edge(clk);
sclk <= CPOL; -- one full glitch, idle level restored
wait until falling_edge(clk);
if viol(V_EDGE) /= '1' then
report " FAIL: a glitch while CS was inactive was not reported";
errs := errs + 1;
end if;
-- The glitch necessarily spends a cycle at the wrong level, so R0
-- fires too -- correctly, rather than as a false positive.
if viol(V_LEAD) = '1' or viol(V_LAG) = '1' or
viol(V_BITS) = '1' or viol(V_GAP) = '1' then
report " FAIL: a glitch set a framing bit"; errs := errs + 1;
end if;
report " R1: glitch while inactive -- edge and level both, correctly";
-- 4. R2 -- the lead too short.
restart;
frame(LEAD - 1, LAG, BITS, CPOL, x"A5");
gap(MIN_HIGH + 2);
expect_only("R2: lead one cycle short", V_LEAD);
-- 5. R3 -- the lag too short, asserted against the MEASURED lag.
restart;
frame(LEAD, LAG - 2, BITS, CPOL, x"A5");
gap(MIN_HIGH + 2);
if to_integer(meas_lag) >= LAG then
report " FAIL: the stimulus did not produce a short lag";
errs := errs + 1;
end if;
expect_only("R3: lag short", V_LAG);
report " measured lag = " & integer'image(to_integer(meas_lag)) &
", required " & integer'image(LAG);
-- 6. R4 -- the wrong bit count, both directions, because a width
-- error can go either way.
restart;
frame(LEAD, LAG, BITS - 1, CPOL, x"A5");
gap(MIN_HIGH + 2);
expect_only("R4: seven bits", V_BITS);
restart;
frame(LEAD, LAG, BITS + 1, CPOL, x"A5");
gap(MIN_HIGH + 2);
expect_only("R4: nine bits", V_BITS);
-- 7. R5 -- the inter-frame gap too short.
restart;
frame(LEAD, LAG, BITS, CPOL, x"A5");
gap(MIN_HIGH - 3);
frame(LEAD, LAG, BITS, CPOL, x"5A");
gap(MIN_HIGH + 2);
if to_integer(meas_gap) >= MIN_HIGH then
report " FAIL: the stimulus did not produce a short gap";
errs := errs + 1;
end if;
expect_only("R5: gap short", V_GAP);
report " measured gap = " & integer'image(to_integer(meas_gap)) &
", required " & integer'image(MIN_HIGH);
-- 8. THE EXACT BOUNDARIES. The stimulus parameters are chosen so the
-- MEASURED intervals land exactly on their minimums, which the
-- assertion confirms -- otherwise this would test a comfortable
-- case and claim to test a boundary.
restart;
frame(LEAD, LAG, BITS, CPOL, x"A5");
gap(MIN_HIGH - 1);
frame(LEAD, LAG - 1, BITS, CPOL, x"A5");
gap(MIN_HIGH + 2);
if to_integer(meas_lead) /= LEAD or to_integer(meas_lag) /= LAG or
to_integer(meas_gap) /= MIN_HIGH then
report " FAIL: the boundary stimulus did not land on the minimums";
errs := errs + 1;
end if;
if ok /= '1' then
report " FAIL: every interval at exactly its minimum was rejected";
errs := errs + 1;
end if;
report " intervals at minimum: lead=" &
integer'image(to_integer(meas_lead)) & " lag=" &
integer'image(to_integer(meas_lag)) & " gap=" &
integer'image(to_integer(meas_gap)) & " -- accepted";
-- 9. SEVERAL VIOLATIONS AT ONCE, so the whole master is fixed in one
-- pass rather than one fault per attempt.
restart;
frame(LEAD - 1, LAG - 2, BITS - 1, CPOL, x"A5");
gap(MIN_HIGH + 2);
if not (viol(V_LEAD) = '1' and viol(V_LAG) = '1' and
viol(V_BITS) = '1') then
report " FAIL: three simultaneous violations were not all reported";
errs := errs + 1;
end if;
report " lead+lag+bits all short -- three bits together";
-- 10. CLEAR restarts the window.
restart;
if ok /= '1' or frames_seen /= 0 then
report " FAIL: clear did not restart the window"; errs := errs + 1;
end if;
for f in 0 to 3 loop
frame(LEAD, LAG, BITS, CPOL, x"3C");
gap(MIN_HIGH + 2);
end loop;
if ok /= '1' then
report " FAIL: a compliant master after violations reported a violation";
errs := errs + 1;
end if;
report " after clear: 4 clean frames, no violation";
-- 11. THE FIRST FRAME AFTER RESET must not be reported as too soon.
wait until falling_edge(clk);
rst_n <= '0';
wait until falling_edge(clk);
rst_n <= '1';
wait until falling_edge(clk);
frame(LEAD, LAG, BITS, CPOL, x"A5");
gap(MIN_HIGH + 2);
if viol(V_GAP) = '1' then
report " FAIL: the first frame after reset was reported as too soon";
errs := errs + 1;
end if;
report " first frame after reset -- not flagged";
errors <= errs;
if errs = 0 then
report "PASS: a compliant master reports no violation and is counted correctly, each of the six requirements is violated independently and sets exactly its own bit, a glitch correctly reports both an edge and a level fault without touching any framing bit, every interval is accepted at exactly its minimum and rejected one cycle below it, several violations are reported together, clear restarts the window, and the first frame after reset is not flagged as too soon";
else
report "FAIL: " & integer'image(errs) & " error(s)" severity error;
end if;
halt <= true;
wait;
end process;
end architecture;Parity
All three implementations report the same six bits and the same three measured intervals, and all three testbenches provoke each requirement in isolation — including the R0-alone sequence of §5, which is the only test in the set that distinguishes a working R0 from a missing one.
7. Why a Verification Engineer Cares
The monitor above is RTL, which makes it usable in synthesis, on an FPGA, and inside a lint-clean build. The same six requirements written as assertions are shorter and say the same thing, and both belong in the flow — the assertions for simulation and formal, the monitor for everything else.
// The six requirements of Chapter 13.1, stated as concurrent assertions.
//
// These are not a replacement for the synthesisable monitor: they cannot run on
// an FPGA and they cannot be carried into a gate-level netlist. They are a
// replacement for the monitor's INTERNAL COUNTERS, which is where its own bugs
// live -- and the two disagreeing is far more informative than either alone.
module spi_master_props_sva #(
parameter int LEAD = 4,
parameter int LAG = 4,
parameter int GAP = 8,
parameter int LEN = 8,
parameter int MIN_HIGH = 2
) (
input logic clk,
input logic rst_n,
input logic cpol,
input logic sclk,
input logic cs_n
);
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
// R0 -- SCLK rests at CPOL while nothing is selected. Stated with a one
// cycle tolerance because SCLK is a REGISTERED output (Chapter 13.4) and
// therefore necessarily takes a cycle to follow a change of CPOL.
property p_idle_level;
cs_n && $stable(cpol) && $past(cs_n) |-> (sclk == cpol);
endproperty
a_idle_level: assert property (p_idle_level);
// R1 -- no SCLK edge while deselected.
property p_no_edge_idle;
cs_n && $past(cs_n) |-> $stable(sclk);
endproperty
a_no_edge_idle: assert property (p_no_edge_idle);
// R2 -- chip select leads the first edge. Written as "once CS falls, SCLK
// may not move for LEAD cycles", which is the same statement without
// needing to know which edge is the first one.
property p_lead;
$fell(cs_n) |=> ($stable(sclk))[*LEAD-1];
endproperty
a_lead: assert property (p_lead);
// R3 -- chip select lags the last edge: SCLK must have been still for LAG
// cycles before CS is allowed to rise.
property p_lag;
$rose(cs_n) |-> $past($stable(sclk), 1) && $past($stable(sclk), LAG-1);
endproperty
a_lag: assert property (p_lag);
// R5 -- the inter-transaction gap.
property p_gap;
$rose(cs_n) |=> (cs_n)[*GAP-1];
endproperty
a_gap: assert property (p_gap);
// R4 -- the bit count. Not expressible as a simple implication, because it
// is a property of a whole assertion window rather than of an edge, so it
// keeps its counter. Counting the edges of a registered signal is exactly
// what assertions are bad at and what the monitor is good at, which is the
// clearest argument for having both.
int edges;
always_ff @(posedge clk) begin
if (!rst_n) edges <= 0;
else if ($fell(cs_n)) edges <= 0;
else if (!cs_n && sclk !== $past(sclk)) edges <= edges + 1;
end
a_bit_count: assert property ($rose(cs_n) |-> ($past(edges) == 2*LEN));
// A pulse narrower than the slave can see is not a clock edge, it is a
// glitch, and it is the one failure that every requirement above tolerates.
property p_min_high;
$rose(sclk) |=> (sclk)[*MIN_HIGH-1];
endproperty
a_min_high: assert property (p_min_high);
endmoduleThe coverage model is the other half, and its shape is the point: it bins intervals relative to their limits, not absolute cycle counts.
// What needs covering here is not "which lead times occurred" -- that is an
// unbounded set and most of it is uninteresting. It is "how close to the limit
// did we get", because a requirement is only verified if the suite went NEAR
// its boundary. A suite that only ever supplies a lead of 50 cycles against a
// limit of 5 has not tested R2; it has avoided it.
covergroup cg_spi_requirements (int LEAD, int LAG, int GAP)
@(posedge clk);
// Distance from the limit, signed: negative means violated, zero means
// exactly at the limit, and that bin is the one that matters.
margin_lead: coverpoint (meas_lead - LEAD) iff (cs_fell) {
bins violated = {[-32:-1]};
bins exact = {0};
bins slack_1_2 = {[1:2]};
bins slack_3_8 = {[3:8]};
bins generous = {[9:$]};
}
margin_lag: coverpoint (meas_lag - LAG) iff (cs_rose) {
bins violated = {[-32:-1]};
bins exact = {0};
bins slack_1_2 = {[1:2]};
bins slack_3_8 = {[3:8]};
bins generous = {[9:$]};
}
margin_gap: coverpoint (meas_gap - GAP) iff (cs_fell) {
bins violated = {[-64:-1]};
bins exact = {0};
bins slack_1_4 = {[1:4]};
bins generous = {[5:$]};
}
// Each requirement must have been provoked ALONE at least once. Without
// this, the R0-and-R1 confusion of section 5 passes coverage closure: both
// bits get hit, and nothing records that they were only ever hit together.
solo: coverpoint viol_solo {
bins r0_only = {6'b000001};
bins r1_only = {6'b000010};
bins r2_only = {6'b000100};
bins r3_only = {6'b001000};
bins r4_only = {6'b010000};
bins r5_only = {6'b100000};
}
// And the clean case, because a monitor that fires on correct behaviour is
// worse than no monitor at all.
clean: coverpoint (viol == 6'b000000) iff (cs_rose) {
bins no_violation = {1};
}
endgroup8. Why an FPGA or ASIC Engineer Cares
A requirements monitor is not a simulation artefact. Three uses justify making it synthesisable:
It runs on the board. Instantiated alongside the master in an FPGA, the six sticky bits and three measured intervals are readable over JTAG or a status register. When a board misbehaves, the question "is the master violating its own timing" is answered in seconds rather than by hooking up a scope — and the measured intervals mean the answer includes a number.
It survives synthesis, so it can be compared against itself. The same monitor in RTL simulation, in gate-level simulation and on the board gives three measurements of the same interval. If they disagree, something in the flow changed the timing, which is exactly the class of problem that gate-level simulation exists to find and that is otherwise invisible.
It is a lint and CDC target. Because it reads only pins, it has no clock-domain assumptions and no hidden dependencies, so it can be dropped into any hierarchy without pulling anything with it.
The cost is small and worth stating precisely: six flops for the sticky bits, three interval counters at the width of the longest interval, and one saturating high-time counter. At a 32-cycle maximum interval that is roughly 30 flops and no arithmetic beyond increment and compare — nothing on the critical path, because every counter is fed by pins that are already registered.
9. Failure Signature — A Master That Works On One Board And Not The Next
Symptom. A flash driver works on every board from the first production run and fails on roughly a third of the second run. The failing boards read 0xFF from flash. Swapping the flash chip between a working and a failing board moves the failure with the chip.
What that rules out. The failure following the chip rules out the FPGA, the layout and the power supply, which is where such an investigation usually starts and where two days usually go. It points at a difference between chips — and the second-run chips came from a different vendor with a pin-compatible part.
What the monitor says. Both boards report R2 clean against the original limits and identical measured intervals, because the master has not changed. The new part's datasheet specifies t_CSS of 10 ns where the original specified 5 ns. At a 100 MHz system clock the master's 5-cycle lead is 50 ns and satisfies both. So R2 is not it either.
Then R4: the failing boards report R4 set, with the monitor recording 18 SCLK edges per assertion where 16 were expected.
The mechanism. The new part requires a dummy byte after the command that the original did not, and the driver's transaction spec was written from the original datasheet. The driver is sending the right number of bytes for the wrong device — and the extra two edges per assertion are the two halves of one extra clock period that the driver's own bit counting produced when it merged the address and dummy phases.
Why the monitor found it and inspection would not. Every timing requirement passes. The data is plausible. The only thing wrong is a count, and R4 is the only requirement in the list that is not a timing property. It is also the requirement most often left out of a requirements document, on the reasoning that the frame length is obviously correct because it is a parameter.
What to do about it. Short term, the driver's transaction spec is regenerated from the correct datasheet. Structurally, the spec's checksum from Chapter 10.6 should be compared against the device ID read back at boot, so a part substitution is detected rather than diagnosed.
10. Common Misconceptions
"If the RTL is correct, the requirements monitor is redundant." The monitor's value is not in catching design bugs — it is in catching specification bugs, which are the ones a correct implementation cannot protect you from. The failure above is a driver sending the wrong number of bytes to a correct master.
"Requirements should be written in nanoseconds, since that is what the datasheet says." The datasheet's nanoseconds have to be converted into system clocks by someone, and that conversion is itself a design decision that can be wrong. Writing the requirements in cycles puts the conversion in one visible place and makes the requirement checkable without a timing model.
"A requirement that no test can violate is a requirement that is trivially satisfied." It is a requirement that is unverified. The two are indistinguishable from a coverage report, which is why the solo coverpoint above exists.
"Sticky bits are worse than immediate assertions, because they lose the time of the violation." They lose the time and keep the fact, which is the right trade for a monitor that has to survive synthesis and be read from a register long after the event. Assertions keep the time and cannot run on the board. Use both — §7 does.
"Measuring the interval is over-engineering; a pass/fail bit is enough." A pass/fail bit tells you a board is broken. A measured interval tells you it is broken by one cycle, which distinguishes a marginal design from a wrong one and often identifies the cause without further work.
11. Reason It Through
A master satisfies all six requirements and the slave still returns wrong data. What class of fault remains?
Everything about when is correct, so the fault is about what: the bit values, their order, or their meaning. The six requirements say nothing about MOSI or MISO — deliberately, because data correctness is checked by comparison against an expected transaction and not by a property over pins. Candidates are bit order (Chapter 13.8), a launch-and-capture assignment that is right for the wrong mode (Chapter 13.5), an alignment error on a frame narrower than the datapath (Chapter 13.6), or a transaction spec that describes a different device.
Requirement R4 is written as "exactly LEN SCLK periods per assertion." Why not "at least"?
Because too many clocks is as bad as too few and fails differently. Too few leaves the slave's shift register misaligned and the transaction incomplete; too many shifts extra bits into it, and for a write command those extra bits may be accepted as part of the payload. "At least" would pass the more dangerous of the two.
Could R5 be checked by a testbench that runs one transaction per simulation?
No, and that is the most useful thing about R5. It is a property of the boundary between two transactions, so it needs at least two in the same run. This generalises: any requirement whose statement contains the word "consecutive", "between" or "after the previous" is invisible to a suite built from independent short tests, and those suites are the default.
The monitor's high_cycles counter saturates instead of wrapping. What breaks if it wraps, and when?
It measures how long SCLK has been away from its idle level, so a wrap makes a very long time look like a very short one. After reset the counter has been counting for an unbounded interval; if it wraps to a small value, the minimum-high-time check sees a short pulse and flags the first frame after reset. The failure therefore appears exactly once per power-on, which makes it look like an initialisation problem in the design under test rather than a bug in the monitor — and a monitor that cries wolf at reset is a monitor that gets disabled.
Two requirements in a list are found to fire together in every test. What should be done?
Determine whether that is a property of the stimulus or of the requirements. If a waveform exists that violates one alone, the stimulus is inadequate and needs the clear-and-continue pattern of §5. If no such waveform exists, the two requirements are not independent, and one of them should be deleted or restated — because keeping both creates the appearance of coverage that does not exist.
12. Understanding Check
13. Summary
A transaction specification describes a device. A requirements document describes a master, and the conversion is a set of decisions that can each be made wrongly.
A requirement is testable when it is observable on pins that exist, has an independent violation, and is measured rather than assumed. Failing any of the three produces a requirement that a suite will mark verified without ever exercising.
The six requirements for a single-slave master are the idle level, the absence of idle edges, the lead, the lag, the bit count, and the inter-transaction gap. Five are timing; R4 is a count, and it is both the most often omitted and the one that produces the most plausible-looking wrong data.
R0 and R1 are not independently violable by the naive stimulus, because arriving at the wrong level is a transition. The fix — cause the event, clear the record, continue the state — generalises to every event-and-state pair in any requirements list.
The monitor publishes six sticky bits and three measured intervals. The measurements are what turn a failure into a number, and three implementation details were each a bug first: counters reload with one, the high-time counter saturates, and every check is edge-referenced.
For verification, the assertions and the monitor are complementary: assertions give the cycle of violation and cannot run on a board; the monitor survives synthesis and can be read from a register. The coverage model bins margins relative to limits, not absolute values, and includes a solo point so that "both bits were hit" cannot masquerade as "both requirements were checked".
For implementation, roughly thirty flops buys an instrument that runs in RTL simulation, in gate-level simulation and on the board — and answers the question "is the master violating its own timing" with a number instead of a scope.
14. What Comes Next
The requirements exist and can be checked. Nothing has been designed.
Chapter 13.2 — Master Microarchitecture makes the first structural decision: what blocks the master consists of, where the boundary between control and datapath falls, and why each block earns its place. The requirements above become the acceptance criteria for that partition — and the first block the partition produces is the one that decides when configuration takes effect, which turns out to be a question with a wrong answer that is very easy to pick.
Continue learning
Related tutorials
- Related topic
Launch and Sample Edges
One edge of each bit time places a bit on the wire, the other captures it, and they must never be the same edge. Why the separation is forced, why it buys half a period, and how RTL maps physical edges onto those roles.
- Related topic
Deriving Mode Behaviour from CPOL and CPHA
The four SPI modes are a two-bit truth table you can rebuild in seconds. The standard numbering, the derivation, the complete mode decoder in three HDLs, and the assertions that keep a configurable design honest.
- Related topic
Command, Address, and Data Phases
How a device layers a transaction onto a raw byte stream: why the opcode decides the shape of everything after it, how a slave tracks phases with no phase marker, and the sequencer that requires in three HDLs.
- Related topic
Dummy Phases and Read Latency
Why a device needs turnaround before it can answer, why dummy is counted in clock cycles rather than bytes, how its length grows with frequency, and the one-byte data offset a mismatch produces.
