SPI · Module 13
Clock Divider and SCLK Generation
A divider that produces SCLK and the two edge strobes a datapath needs: why the strobes must not be called launch and capture, why an odd divisor puts the longer half first, and why SCLK must be a registered output rather than a gated clock.
Chapter 13.3 built a machine that knows when the clock should run. It does not produce one, and producing one is less obvious than it looks.
A divider with a divisor of 5 has to make a period out of five system clocks. Which half gets three of them?
There is a right answer, it depends on what the slave has to do in each half, and a design that picks the other one gives the returning data less time than an even divisor would.
1. SCLK Is Not The Only Output
A divider that produces only SCLK is not enough. The datapath needs to know when each edge happens, one system clock at a time, so it can put a bit on MOSI on one edge and sample MISO on the other. So the divider produces three things:
sclk the pin
edge_a_stb a one-cycle pulse on the LEADING edge (SCLK departs from idle)
edge_b_stb a one-cycle pulse on the TRAILING edge (SCLK returns to idle)
bit_done a one-cycle pulse when a full period has elapsedNow notice what is not in that list: launch and capture. Which of the two edges launches a bit and which captures one depends on CPHA, and that mapping is Chapter 13.5's job.
This is not fussiness about naming. If the divider emitted launch_stb and capture_stb, it would need cpha as an input — and a divider that depends on the mode has to be re-verified every time the mode logic changes. Keeping the two blocks independent means the divider's 46-configuration sweep in §6 is valid for all four modes without running it four times.
2. Leading and Trailing, Not Rising and Falling
The strobes are defined relative to the idle level, not to the voltage:
CPOL = 0 idle low leading = rising trailing = falling
CPOL = 1 idle high leading = falling trailing = risingThat definition is what lets CPOL disappear from every block downstream. The divider is the only place in the master that knows which voltage idle is, and it absorbs the whole of CPOL by choosing which way the pin moves first. Everything after it sees "the first edge" and "the second edge", and inverting CPOL inverts the pin and moves nothing else.
The alternative — naming them sclk_rise and sclk_fall and letting the datapath work out which is which — pushes CPOL into the mode logic, which then has four cases instead of two. Chapter 13.5 is entirely about why that matters.
3. The Odd-Divisor Decision
div is the SCLK period measured in system clocks. For an even value the two halves are equal. For an odd value they cannot be, and the remainder has to go somewhere:
div = 5, first half longer: ___/‾‾‾\__ 3 cycles away, 2 back
div = 5, second half longer: ___/‾‾\___ 2 cycles away, 3 backThe decision follows from what happens in each half. In every mode, one edge launches a bit and the other captures one, and the interval between them is what the round trip has to fit into:
launch edge
|
|<--- slave's t_V, plus the round-trip delay --->|
| |
+-----------------------------------------------+
capture edgeThe launch edge is either the leading edge (CPHA=1) or the trailing edge (CPHA=0). In both cases the capture edge is the other one, and the interval from launch to capture is one of the two halves. So the question becomes: which half is the launch-to-capture interval?
For CPHA=1, launch is on the leading edge and capture on the trailing, so launch-to-capture is the first half. For CPHA=0 it is the second half of one period into the first half of the next — which is a full half-period either way.
Since the first half is the launch-to-capture interval in the CPHA=1 case and neither case is disadvantaged by a longer first half, the first half gets the remainder:
half_a = ceil(div / 2) the launch-to-capture half in CPHA=1
half_b = floor(div / 2)Choosing the other way would give the returning data less time at div=5 than it gets at div=4, which is the wrong direction for a parameter whose whole purpose is to slow the interface down.
4. The Block
5. What The Waveform Looks Like
Two things in that figure are worth reading carefully.
bit_done is not at the trailing edge. It fires at the end of the second half, which is half a period after the trailing edge. A consumer that counted trailing edges as periods would start the next bit half a period early, and at a divisor of 2 that is one system clock — which usually still works and occasionally does not.
The strobes land one cycle after the pin moves. SCLK is a registered output, so the pin changes at the clock edge that sets the register, and the strobe is registered on the same edge and therefore high during the following cycle. Every consumer in this module accounts for that, and the monitors in the testbenches compare strobes against the pin one cycle apart for the same reason.
6. Building the Divider — Three HDLs
The circuit
One counter, one phase bit, four registered outputs. Three decisions are embedded in it and each was a bug at some point:
SCLK is a register, not a gated clock. sclk is the output of a flop whose value is set by the phase logic. It is never clk & something, never a clock-tree signal, and nothing in this design is clocked on it. That is what makes the block synthesisable without any clock-gating cells or constraints, and it is why the master has exactly one clock domain.
When disabled, the counter is held ready rather than left where it stopped. On en deasserting, the counter and phase bit are cleared so that the first edge after re-enabling is a full half-period away. Leaving the counter where it stopped means the first bit of the next frame has a random-length first half, which is a real timing violation that depends on how long the previous frame was.
A divisor below two holds the line idle and reports. There is no correct clock for div of 0 or 1, and improvising one — toggling every cycle, say — produces a clock the slave will sample and a design that has silently reinterpreted its configuration. Holding SCLK at its idle level means the device sees no clock at all, which is a state every slave handles, and div_err makes the cause visible.
// spi_clkdiv_strobe.sv
//
// Chapter 13.4 -- the divider, and the two strobes it really has to produce.
//
// A divider that only produces SCLK is not enough. The datapath needs to know
// WHEN each edge happens, one system clock at a time, so it can launch a bit
// on one and capture on the other. So this block emits three things:
//
// sclk the pin
// edge_a_stb a one-cycle pulse on the LEADING edge (away from idle)
// edge_b_stb a one-cycle pulse on the TRAILING edge (back to idle)
//
// Note what it does NOT emit: launch and capture. Which of a and b launches
// depends on CPHA, and that mapping is Chapter 13.5's job. Putting it here
// would mean this block needed the mode, and a divider that needs the mode is
// a divider that has to be re-verified when the mode logic changes.
//
// THE ODD-DIVISOR DECISION. `div` is the SCLK period in system clocks, and
// for an odd value the two halves cannot be equal. The choice made here is
// that the FIRST half is the longer one:
//
// div = 5 -> 3 cycles from edge A, then 2 cycles to edge B
//
// That is not arbitrary. The interval between the launching edge and the
// capturing edge is what the slave's t_V and the round trip have to fit into
// (Chapter 9.4), so the longer half belongs there. Choosing the other way
// would give the returning data LESS time than an even divisor would, which
// is the opposite of what an odd divisor should cost.
module spi_clkdiv_strobe #(
parameter int DIV_W = 8
) (
input logic clk,
input logic rst_n,
input logic en, // the FSM's shift_en
input logic [DIV_W-1:0] div, // SCLK period in clk cycles, >= 2
input logic cpol, // the idle level
output logic sclk,
output logic edge_a_stb, // leading edge, away from idle
output logic edge_b_stb, // trailing edge, back to idle
output logic bit_done, // one full SCLK period elapsed
output wire [DIV_W-1:0] half_a, // the two half-period lengths,
output wire [DIV_W-1:0] half_b, // published so they can be checked
output wire div_err // div < 2 is not a divider
);
// The halves. Integer division by two is a shift, and the remainder goes
// to the FIRST half -- the decision argued for in the header.
assign half_a = (div + {{(DIV_W-1){1'b0}}, 1'b1}) >> 1; // ceil(div/2)
assign half_b = div >> 1; // floor(div/2)
assign div_err = (div < {{(DIV_W-2){1'b0}}, 2'b10});
logic [DIV_W-1:0] cnt;
logic in_b; // in the second half of the period
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {DIV_W{1'b0}};
in_b <= 1'b0;
sclk <= 1'b0;
edge_a_stb <= 1'b0;
edge_b_stb <= 1'b0;
bit_done <= 1'b0;
end else begin
edge_a_stb <= 1'b0;
edge_b_stb <= 1'b0;
bit_done <= 1'b0;
if (!en) begin
// Not shifting: SCLK rests at the idle level and the counter
// is held ready, so the first edge after enable is a full
// half-period away rather than wherever the counter happened
// to stop.
sclk <= cpol;
cnt <= {DIV_W{1'b0}};
in_b <= 1'b0;
end else if (div_err) begin
// A divisor below two cannot produce an SCLK period at all.
// Holding the line idle is the safe response: a device sees no
// clock rather than a malformed one.
sclk <= cpol;
end else if (!in_b) begin
// First half: SCLK is away from idle.
if (cnt == {DIV_W{1'b0}}) begin
sclk <= ~cpol;
edge_a_stb <= 1'b1;
end
if (cnt >= half_a - {{(DIV_W-1){1'b0}}, 1'b1}) begin
cnt <= {DIV_W{1'b0}};
in_b <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
end
end else begin
// Second half: SCLK returns to idle.
if (cnt == {DIV_W{1'b0}}) begin
sclk <= cpol;
edge_b_stb <= 1'b1;
end
if (cnt >= half_b - {{(DIV_W-1){1'b0}}, 1'b1}) begin
cnt <= {DIV_W{1'b0}};
in_b <= 1'b0;
// The period is complete at the END of the second half,
// not at the trailing edge -- a consumer that counted
// trailing edges would start the next bit half a period
// early.
bit_done <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
end
end
end
end
endmodule// spi_clkdiv_strobe_tb.sv
//
// Every interval here is MEASURED from the waveform the divider produces,
// not inferred from its parameters -- because the parameters are what is
// under test. The checks then assert relationships that must hold for every
// divisor: the halves sum to the period, each strobe fires exactly once per
// period, and the first half is never shorter than the second.
`timescale 1ns/1ps
module spi_clkdiv_strobe_tb;
localparam int DIV_W = 8;
logic clk = 1'b0;
logic rst_n = 1'b0;
always #5 clk = ~clk;
logic en = 1'b0;
logic [DIV_W-1:0] div = 8'd4;
logic cpol = 1'b0;
wire sclk, edge_a_stb, edge_b_stb, bit_done;
wire [DIV_W-1:0] half_a, half_b;
wire div_err;
int errors = 0;
int d, k;
int m_half_a, m_half_b, m_period;
int n_a, n_b, n_done, n_periods;
int since_a, since_b;
int idle_wrong;
// SCLK is a REGISTERED output, so it necessarily takes one clock to
// follow a change of en or cpol. That one-cycle lag is the property that
// makes it glitch-free, so the idle check must allow for it rather than
// treat it as a fault.
logic en_q, cpol_q;
spi_clkdiv_strobe #(.DIV_W(DIV_W)) dut (
.clk(clk), .rst_n(rst_n),
.en(en), .div(div), .cpol(cpol),
.sclk(sclk), .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb),
.bit_done(bit_done),
.half_a(half_a), .half_b(half_b), .div_err(div_err)
);
// Measure the halves from the strobes themselves: A to B is the first
// half, B to the next A is the second.
always_ff @(posedge clk) begin
if (!rst_n) begin
n_a <= 0; n_b <= 0; n_done <= 0; n_periods <= 0;
since_a <= 0; since_b <= 0;
m_half_a <= 0; m_half_b <= 0; m_period <= 0;
idle_wrong <= 0;
end else begin
if (edge_a_stb) begin
n_a <= n_a + 1;
since_a <= 1;
m_half_b <= since_b; // B .. A is the second half
m_period <= since_b + m_half_a;
end else begin
since_a <= since_a + 1;
end
if (edge_b_stb) begin
n_b <= n_b + 1;
since_b <= 1;
m_half_a <= since_a; // A .. B is the first half
end else begin
since_b <= since_b + 1;
end
if (bit_done) n_done <= n_done + 1;
if (bit_done) n_periods <= n_periods + 1;
// While disabled the line must rest at the idle level -- checked
// only once en and cpol have both been stable for a cycle.
en_q <= en;
cpol_q <= cpol;
if (!en && !en_q && (cpol == cpol_q) && (sclk !== cpol))
idle_wrong <= idle_wrong + 1;
end
end
task automatic run_div(input int dv, input bit pol, input int periods);
begin
@(negedge clk);
en = 1'b0; div = DIV_W'(dv); cpol = pol;
repeat (3) @(negedge clk);
n_a = 0; n_b = 0; n_done = 0;
en = 1'b1;
// Run for the requested number of periods plus slack.
repeat (dv * periods + dv + 4) @(negedge clk);
en = 1'b0;
repeat (3) @(negedge clk);
end
endtask
initial begin
n_a = 0; n_b = 0; n_done = 0; n_periods = 0;
since_a = 0; since_b = 0;
m_half_a = 0; m_half_b = 0; m_period = 0; idle_wrong = 0;
en_q = 1'b0; cpol_q = 1'b0;
repeat (3) @(negedge clk);
rst_n = 1'b1;
@(negedge clk);
// 1. THE IDLE LEVEL. Disabled, SCLK rests at CPOL -- for both
// polarities, because a divider that only ever rests low passes a
// CPOL=0 test and fails silently on a mode-3 device.
cpol = 1'b0; repeat (4) @(negedge clk);
if (sclk !== 1'b0) begin
$display(" FAIL: disabled with CPOL=0, SCLK is %0b", sclk);
errors++;
end
cpol = 1'b1; repeat (4) @(negedge clk);
if (sclk !== 1'b1) begin
$display(" FAIL: disabled with CPOL=1, SCLK is %0b", sclk);
errors++;
end
cpol = 1'b0;
$display(" idle: SCLK rests at CPOL for both polarities");
// 2. AN EVEN DIVISOR. Equal halves, and the period is the divisor.
run_div(4, 1'b0, 6);
if (m_half_a != 2 || m_half_b != 2 || m_period != 4) begin
$display(" FAIL: div=4 measured halves %0d/%0d, period %0d",
m_half_a, m_half_b, m_period);
errors++;
end
$display(" div=4: halves %0d/%0d, period %0d, %0d A-edges %0d B-edges %0d bit_done",
m_half_a, m_half_b, m_period, n_a, n_b, n_done);
// 3. AN ODD DIVISOR. The halves differ by one and the FIRST is the
// longer -- the decision the header argues for, checked rather
// than assumed.
run_div(5, 1'b0, 6);
if (m_half_a != 3 || m_half_b != 2 || m_period != 5) begin
$display(" FAIL: div=5 measured halves %0d/%0d, period %0d",
m_half_a, m_half_b, m_period);
errors++;
end
if (m_half_a <= m_half_b) begin
$display(" FAIL: on an odd divisor the first half is not the longer one");
errors++;
end
$display(" div=5: halves %0d/%0d -- the launch-to-capture half is the longer one",
m_half_a, m_half_b);
// 4. THE SMALLEST LEGAL DIVISOR. div=2 toggles every system clock,
// which is the fastest SCLK a synchronous divider can make.
run_div(2, 1'b0, 8);
if (m_half_a != 1 || m_half_b != 1 || m_period != 2) begin
$display(" FAIL: div=2 measured halves %0d/%0d, period %0d",
m_half_a, m_half_b, m_period);
errors++;
end
if (div_err) begin
$display(" FAIL: div=2 was reported as an error"); errors++;
end
$display(" div=2: halves %0d/%0d -- the fastest a synchronous divider can produce",
m_half_a, m_half_b);
// 5. AN ILLEGAL DIVISOR. Below two there is no period to produce, so
// it is reported and the line is held idle rather than toggling at
// some improvised rate.
@(negedge clk); div = 8'd1; cpol = 1'b0; en = 1'b1;
repeat (8) @(negedge clk);
if (!div_err) begin
$display(" FAIL: div=1 was not reported as illegal"); errors++;
end
if (sclk !== 1'b0) begin
$display(" FAIL: an illegal divisor produced a clock"); errors++;
end
@(negedge clk); div = 8'd0;
repeat (8) @(negedge clk);
if (!div_err || sclk !== 1'b0) begin
$display(" FAIL: div=0 was not held idle and reported"); errors++;
end
en = 1'b0; div = 8'd4;
repeat (3) @(negedge clk);
$display(" div<2: reported, and the line is held idle rather than improvised");
// 6. STROBE COUNTS. Each strobe fires exactly once per period, and
// bit_done once per period too -- so a consumer counting any of
// the three counts the same number of bits.
run_div(6, 1'b0, 10);
// A run does not stop on a period boundary, so the counts are not
// equal -- they are in STEP. Each period produces A then B then done,
// so at any instant the three counts differ by at most one and never
// go out of order. That is the real invariant, and it holds at every
// cycle rather than only at boundaries.
if ((n_a - n_b) > 1 || (n_a - n_b) < 0 ||
(n_b - n_done) > 1 || (n_b - n_done) < 0) begin
$display(" FAIL: strobe counts out of step -- A=%0d B=%0d done=%0d",
n_a, n_b, n_done);
errors++;
end
if (n_done < 9) begin
$display(" FAIL: only %0d periods in a run of 10", n_done);
errors++;
end
$display(" strobes: A=%0d B=%0d bit_done=%0d -- in step, one of each per period",
n_a, n_b, n_done);
// 7. THE SWEEP. For every divisor from 2 to 24, both polarities, the
// measured halves must match the published ones and sum to the
// period. This is the relationship that must hold for every
// divisor rather than for the three that were checked by hand.
for (d = 2; d <= 24; d++) begin
for (k = 0; k < 2; k++) begin
run_div(d, k[0], 4);
if (m_period != d) begin
$display(" FAIL: div=%0d cpol=%0b measured a period of %0d",
d, k[0], m_period);
errors++;
end
if ((m_half_a + m_half_b) != d) begin
$display(" FAIL: div=%0d halves %0d+%0d do not sum to the period",
d, m_half_a, m_half_b);
errors++;
end
if (m_half_a != int'(half_a) || m_half_b != int'(half_b)) begin
$display(" FAIL: div=%0d measured %0d/%0d but published %0d/%0d",
d, m_half_a, m_half_b, half_a, half_b);
errors++;
end
if (m_half_a < m_half_b) begin
$display(" FAIL: div=%0d first half %0d is shorter than second %0d",
d, m_half_a, m_half_b);
errors++;
end
if ((n_a - n_b) > 1 || (n_a - n_b) < 0 ||
(n_b - n_done) > 1 || (n_b - n_done) < 0) begin
$display(" FAIL: div=%0d strobe counts out of step (A=%0d B=%0d done=%0d)",
d, n_a, n_b, n_done);
errors++;
end
end
end
$display(" 46 (divisor, polarity) pairs swept: measured halves match the published ones, sum to the period, and the first is never shorter");
// 8. THE LINE NEVER MOVED WHILE DISABLED, across everything above.
if (idle_wrong != 0) begin
$display(" FAIL: SCLK left the idle level on %0d disabled cycles",
idle_wrong);
errors++;
end
$display(" disabled: 0 cycles on which SCLK left the idle level");
if (errors == 0)
$display("PASS: SCLK rests at CPOL for both polarities whenever the divider is disabled and never left it once, an even divisor gives equal halves and an odd one gives a longer FIRST half so the launch-to-capture interval is the longer of the two, the smallest legal divisor of two toggles every system clock, a divisor below two is reported and the line held idle rather than improvised, and across 46 divisor and polarity pairs the measured halves match the published ones, sum to the period, and produce exactly one A strobe, one B strobe and one bit_done per period");
else
$display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule// spi_clkdiv_strobe.v
//
// Chapter 13.4 -- the divider, and the two strobes it really has to produce.
//
// A divider that only produces SCLK is not enough. The datapath needs to know
// WHEN each edge happens, one system clock at a time, so it can launch a bit
// on one and capture on the other. So this block emits three things:
//
// sclk the pin
// edge_a_stb a one-cycle pulse on the LEADING edge (away from idle)
// edge_b_stb a one-cycle pulse on the TRAILING edge (back to idle)
//
// Note what it does NOT emit: launch and capture. Which of a and b launches
// depends on CPHA, and that mapping is Chapter 13.5's job. Putting it here
// would mean this block needed the mode, and a divider that needs the mode is
// a divider that has to be re-verified when the mode logic changes.
//
// THE ODD-DIVISOR DECISION. `div` is the SCLK period in system clocks, and
// for an odd value the two halves cannot be equal. The choice made here is
// that the FIRST half is the longer one:
//
// div = 5 -> 3 cycles from edge A, then 2 cycles to edge B
//
// That is not arbitrary. The interval between the launching edge and the
// capturing edge is what the slave's t_V and the round trip have to fit into
// (Chapter 9.4), so the longer half belongs there. Choosing the other way
// would give the returning data LESS time than an even divisor would, which
// is the opposite of what an odd divisor should cost.
module spi_clkdiv_strobe #(
parameter DIV_W = 8
) (
input wire clk,
input wire rst_n,
input wire en, // the FSM's shift_en
input wire [DIV_W-1:0] div, // SCLK period in clk cycles, >= 2
input wire cpol, // the idle level
output reg sclk,
output reg edge_a_stb, // leading edge, away from idle
output reg edge_b_stb, // trailing edge, back to idle
output reg bit_done, // one full SCLK period elapsed
output wire [DIV_W-1:0] half_a, // the two half-period lengths,
output wire [DIV_W-1:0] half_b, // published so they can be checked
output wire div_err // div < 2 is not a divider
);
// The halves. Integer division by two is a shift, and the remainder goes
// to the FIRST half -- the decision argued for in the header.
assign half_a = (div + {{(DIV_W-1){1'b0}}, 1'b1}) >> 1; // ceil(div/2)
assign half_b = div >> 1; // floor(div/2)
assign div_err = (div < {{(DIV_W-2){1'b0}}, 2'b10});
reg [DIV_W-1:0] cnt;
reg in_b; // in the second half of the period
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {DIV_W{1'b0}};
in_b <= 1'b0;
sclk <= 1'b0;
edge_a_stb <= 1'b0;
edge_b_stb <= 1'b0;
bit_done <= 1'b0;
end else begin
edge_a_stb <= 1'b0;
edge_b_stb <= 1'b0;
bit_done <= 1'b0;
if (!en) begin
// Not shifting: SCLK rests at the idle level and the counter
// is held ready, so the first edge after enable is a full
// half-period away rather than wherever the counter happened
// to stop.
sclk <= cpol;
cnt <= {DIV_W{1'b0}};
in_b <= 1'b0;
end else if (div_err) begin
// A divisor below two cannot produce an SCLK period at all.
// Holding the line idle is the safe response: a device sees no
// clock rather than a malformed one.
sclk <= cpol;
end else if (!in_b) begin
// First half: SCLK is away from idle.
if (cnt == {DIV_W{1'b0}}) begin
sclk <= ~cpol;
edge_a_stb <= 1'b1;
end
if (cnt >= half_a - {{(DIV_W-1){1'b0}}, 1'b1}) begin
cnt <= {DIV_W{1'b0}};
in_b <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
end
end else begin
// Second half: SCLK returns to idle.
if (cnt == {DIV_W{1'b0}}) begin
sclk <= cpol;
edge_b_stb <= 1'b1;
end
if (cnt >= half_b - {{(DIV_W-1){1'b0}}, 1'b1}) begin
cnt <= {DIV_W{1'b0}};
in_b <= 1'b0;
// The period is complete at the END of the second half,
// not at the trailing edge -- a consumer that counted
// trailing edges would start the next bit half a period
// early.
bit_done <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
end
end
end
end
endmodule// spi_clkdiv_strobe_tb.v
//
// Every interval here is MEASURED from the waveform the divider produces,
// not inferred from its parameters -- because the parameters are what is
// under test. The checks then assert relationships that must hold for every
// divisor: the halves sum to the period, each strobe fires exactly once per
// period, and the first half is never shorter than the second.
`timescale 1ns/1ps
module spi_clkdiv_strobe_tb;
localparam DIV_W = 8;
reg clk;
reg rst_n;
always #5 clk = ~clk;
reg en;
reg [DIV_W-1:0] div;
reg cpol;
wire sclk, edge_a_stb, edge_b_stb, bit_done;
wire [DIV_W-1:0] half_a, half_b;
wire div_err;
integer errors;
integer d, k;
integer m_half_a, m_half_b, m_period;
integer n_a, n_b, n_done, n_periods;
integer since_a, since_b;
integer idle_wrong;
// SCLK is a REGISTERED output, so it necessarily takes one clock to
// follow a change of en or cpol. That one-cycle lag is the property that
// makes it glitch-free, so the idle check must allow for it rather than
// treat it as a fault.
reg en_q, cpol_q;
spi_clkdiv_strobe #(.DIV_W(DIV_W)) dut (
.clk(clk), .rst_n(rst_n),
.en(en), .div(div), .cpol(cpol),
.sclk(sclk), .edge_a_stb(edge_a_stb), .edge_b_stb(edge_b_stb),
.bit_done(bit_done),
.half_a(half_a), .half_b(half_b), .div_err(div_err)
);
// Measure the halves from the strobes themselves: A to B is the first
// half, B to the next A is the second.
always @(posedge clk) begin
if (!rst_n) begin
n_a <= 0; n_b <= 0; n_done <= 0; n_periods <= 0;
since_a <= 0; since_b <= 0;
m_half_a <= 0; m_half_b <= 0; m_period <= 0;
idle_wrong <= 0;
end else begin
if (edge_a_stb) begin
n_a <= n_a + 1;
since_a <= 1;
m_half_b <= since_b; // B .. A is the second half
m_period <= since_b + m_half_a;
end else begin
since_a <= since_a + 1;
end
if (edge_b_stb) begin
n_b <= n_b + 1;
since_b <= 1;
m_half_a <= since_a; // A .. B is the first half
end else begin
since_b <= since_b + 1;
end
if (bit_done) n_done <= n_done + 1;
if (bit_done) n_periods <= n_periods + 1;
// While disabled the line must rest at the idle level -- checked
// only once en and cpol have both been stable for a cycle.
en_q <= en;
cpol_q <= cpol;
if (!en && !en_q && (cpol == cpol_q) && (sclk !== cpol))
idle_wrong <= idle_wrong + 1;
end
end
task run_div;
input integer dv;
input pol;
input integer periods;
begin
@(negedge clk);
en = 1'b0; div = (dv); cpol = pol;
repeat (3) @(negedge clk);
n_a = 0; n_b = 0; n_done = 0;
en = 1'b1;
// Run for the requested number of periods plus slack.
repeat (dv * periods + dv + 4) @(negedge clk);
en = 1'b0;
repeat (3) @(negedge clk);
end
endtask
initial begin
n_a = 0; n_b = 0; n_done = 0; n_periods = 0;
since_a = 0; since_b = 0;
m_half_a = 0; m_half_b = 0; m_period = 0; idle_wrong = 0;
en_q = 1'b0; cpol_q = 1'b0;
repeat (3) @(negedge clk);
rst_n = 1'b1;
@(negedge clk);
// 1. THE IDLE LEVEL. Disabled, SCLK rests at CPOL -- for both
// polarities, because a divider that only ever rests low passes a
// CPOL=0 test and fails silently on a mode-3 device.
cpol = 1'b0; repeat (4) @(negedge clk);
if (sclk !== 1'b0) begin
$display(" FAIL: disabled with CPOL=0, SCLK is %0b", sclk);
errors = errors + 1;
end
cpol = 1'b1; repeat (4) @(negedge clk);
if (sclk !== 1'b1) begin
$display(" FAIL: disabled with CPOL=1, SCLK is %0b", sclk);
errors = errors + 1;
end
cpol = 1'b0;
$display(" idle: SCLK rests at CPOL for both polarities");
// 2. AN EVEN DIVISOR. Equal halves, and the period is the divisor.
run_div(4, 1'b0, 6);
if (m_half_a != 2 || m_half_b != 2 || m_period != 4) begin
$display(" FAIL: div=4 measured halves %0d/%0d, period %0d",
m_half_a, m_half_b, m_period);
errors = errors + 1;
end
$display(" div=4: halves %0d/%0d, period %0d, %0d A-edges %0d B-edges %0d bit_done",
m_half_a, m_half_b, m_period, n_a, n_b, n_done);
// 3. AN ODD DIVISOR. The halves differ by one and the FIRST is the
// longer -- the decision the header argues for, checked rather
// than assumed.
run_div(5, 1'b0, 6);
if (m_half_a != 3 || m_half_b != 2 || m_period != 5) begin
$display(" FAIL: div=5 measured halves %0d/%0d, period %0d",
m_half_a, m_half_b, m_period);
errors = errors + 1;
end
if (m_half_a <= m_half_b) begin
$display(" FAIL: on an odd divisor the first half is not the longer one");
errors = errors + 1;
end
$display(" div=5: halves %0d/%0d -- the launch-to-capture half is the longer one",
m_half_a, m_half_b);
// 4. THE SMALLEST LEGAL DIVISOR. div=2 toggles every system clock,
// which is the fastest SCLK a synchronous divider can make.
run_div(2, 1'b0, 8);
if (m_half_a != 1 || m_half_b != 1 || m_period != 2) begin
$display(" FAIL: div=2 measured halves %0d/%0d, period %0d",
m_half_a, m_half_b, m_period);
errors = errors + 1;
end
if (div_err) begin
$display(" FAIL: div=2 was reported as an error"); errors = errors + 1;
end
$display(" div=2: halves %0d/%0d -- the fastest a synchronous divider can produce",
m_half_a, m_half_b);
// 5. AN ILLEGAL DIVISOR. Below two there is no period to produce, so
// it is reported and the line is held idle rather than toggling at
// some improvised rate.
@(negedge clk); div = 8'd1; cpol = 1'b0; en = 1'b1;
repeat (8) @(negedge clk);
if (!div_err) begin
$display(" FAIL: div=1 was not reported as illegal"); errors = errors + 1;
end
if (sclk !== 1'b0) begin
$display(" FAIL: an illegal divisor produced a clock"); errors = errors + 1;
end
@(negedge clk); div = 8'd0;
repeat (8) @(negedge clk);
if (!div_err || sclk !== 1'b0) begin
$display(" FAIL: div=0 was not held idle and reported"); errors = errors + 1;
end
en = 1'b0; div = 8'd4;
repeat (3) @(negedge clk);
$display(" div<2: reported, and the line is held idle rather than improvised");
// 6. STROBE COUNTS. Each strobe fires exactly once per period, and
// bit_done once per period too -- so a consumer counting any of
// the three counts the same number of bits.
run_div(6, 1'b0, 10);
// A run does not stop on a period boundary, so the counts are not
// equal -- they are in STEP. Each period produces A then B then done,
// so at any instant the three counts differ by at most one and never
// go out of order. That is the real invariant, and it holds at every
// cycle rather than only at boundaries.
if ((n_a - n_b) > 1 || (n_a - n_b) < 0 ||
(n_b - n_done) > 1 || (n_b - n_done) < 0) begin
$display(" FAIL: strobe counts out of step -- A=%0d B=%0d done=%0d",
n_a, n_b, n_done);
errors = errors + 1;
end
if (n_done < 9) begin
$display(" FAIL: only %0d periods in a run of 10", n_done);
errors = errors + 1;
end
$display(" strobes: A=%0d B=%0d bit_done=%0d -- in step, one of each per period",
n_a, n_b, n_done);
// 7. THE SWEEP. For every divisor from 2 to 24, both polarities, the
// measured halves must match the published ones and sum to the
// period. This is the relationship that must hold for every
// divisor rather than for the three that were checked by hand.
for (d = 2; d <= 24; d = d + 1) begin
for (k = 0; k < 2; k = k + 1) begin
run_div(d, k[0], 4);
if (m_period != d) begin
$display(" FAIL: div=%0d cpol=%0b measured a period of %0d",
d, k[0], m_period);
errors = errors + 1;
end
if ((m_half_a + m_half_b) != d) begin
$display(" FAIL: div=%0d halves %0d+%0d do not sum to the period",
d, m_half_a, m_half_b);
errors = errors + 1;
end
if (m_half_a != (half_a) || m_half_b != (half_b)) begin
$display(" FAIL: div=%0d measured %0d/%0d but published %0d/%0d",
d, m_half_a, m_half_b, half_a, half_b);
errors = errors + 1;
end
if (m_half_a < m_half_b) begin
$display(" FAIL: div=%0d first half %0d is shorter than second %0d",
d, m_half_a, m_half_b);
errors = errors + 1;
end
if ((n_a - n_b) > 1 || (n_a - n_b) < 0 ||
(n_b - n_done) > 1 || (n_b - n_done) < 0) begin
$display(" FAIL: div=%0d strobe counts out of step (A=%0d B=%0d done=%0d)",
d, n_a, n_b, n_done);
errors = errors + 1;
end
end
end
$display(" 46 (divisor, polarity) pairs swept: measured halves match the published ones, sum to the period, and the first is never shorter");
// 8. THE LINE NEVER MOVED WHILE DISABLED, across everything above.
if (idle_wrong != 0) begin
$display(" FAIL: SCLK left the idle level on %0d disabled cycles",
idle_wrong);
errors = errors + 1;
end
$display(" disabled: 0 cycles on which SCLK left the idle level");
if (errors == 0)
$display("PASS: SCLK rests at CPOL for both polarities whenever the divider is disabled and never left it once, an even divisor gives equal halves and an odd one gives a longer FIRST half so the launch-to-capture interval is the longer of the two, the smallest legal divisor of two toggles every system clock, a divisor below two is reported and the line held idle rather than improvised, and across 46 divisor and polarity pairs the measured halves match the published ones, sum to the period, and produce exactly one A strobe, one B strobe and one bit_done per period");
else
$display("FAIL: %0d error(s)", errors);
$finish;
end
initial begin
clk = 1'b0;
rst_n = 1'b0;
en = 1'b0;
div = 8'd4;
cpol = 1'b0;
errors = 0;
end
endmodule-- spi_clkdiv_strobe.vhd
--
-- Chapter 13.4 -- the divider, and the two strobes it really has to produce,
-- in VHDL.
--
-- A divider that only produces SCLK is not enough. The datapath needs to know
-- WHEN each edge happens, one system clock at a time, so it can launch a bit
-- on one and capture on the other:
--
-- sclk the pin
-- edge_a_stb a one-cycle pulse on the LEADING edge (away from idle)
-- edge_b_stb a one-cycle pulse on the TRAILING edge (back to idle)
--
-- Note what it does NOT emit: launch and capture. Which of a and b launches
-- depends on CPHA, and that mapping is Chapter 13.5's job. Putting it here
-- would mean this block needed the mode, and a divider that needs the mode
-- has to be re-verified whenever the mode logic changes.
--
-- THE ODD-DIVISOR DECISION. `div` is the SCLK period in system clocks, and
-- for an odd value the halves cannot be equal. The FIRST half is made the
-- longer one, because the interval between the launching edge and the
-- capturing edge is what the slave's output valid time and the round trip
-- must fit into. Choosing the other way would give the returning data LESS
-- time than an even divisor would.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity spi_clkdiv_strobe is
generic (
DIV_W : positive := 8
);
port (
clk : in std_logic;
rst_n : in std_logic;
en : in std_logic; -- the FSM's shift_en
div : in unsigned(DIV_W - 1 downto 0); -- SCLK period, >= 2
cpol : in std_logic; -- the idle level
sclk : out std_logic;
edge_a_stb : out std_logic; -- leading edge, away from idle
edge_b_stb : out std_logic; -- trailing edge, back to idle
bit_done : out std_logic; -- one full SCLK period elapsed
half_a : out unsigned(DIV_W - 1 downto 0);
half_b : out unsigned(DIV_W - 1 downto 0);
div_err : out std_logic
);
end entity;
architecture rtl of spi_clkdiv_strobe is
-- The halves. Integer division by two is a shift, and the remainder goes
-- to the FIRST half -- the decision argued for in the header.
signal h_a : unsigned(DIV_W - 1 downto 0) := (others => '0');
signal h_b : unsigned(DIV_W - 1 downto 0) := (others => '0');
signal derr : std_logic := '1';
signal cnt : unsigned(DIV_W - 1 downto 0) := (others => '0');
signal in_b : std_logic := '0';
signal sclk_r : std_logic := '0';
signal a_r : std_logic := '0';
signal b_r : std_logic := '0';
signal done_r : std_logic := '0';
begin
h_a <= shift_right(div + 1, 1); -- ceil(div/2)
h_b <= shift_right(div, 1); -- floor(div/2)
derr <= '1' when to_integer(div) < 2 else '0';
half_a <= h_a;
half_b <= h_b;
div_err <= derr;
sclk <= sclk_r;
edge_a_stb <= a_r;
edge_b_stb <= b_r;
bit_done <= done_r;
divide : process (clk, rst_n)
begin
if rst_n = '0' then
cnt <= (others => '0');
in_b <= '0';
sclk_r <= '0';
a_r <= '0';
b_r <= '0';
done_r <= '0';
elsif rising_edge(clk) then
a_r <= '0';
b_r <= '0';
done_r <= '0';
if en = '0' then
-- Not shifting: SCLK rests at the idle level and the counter
-- is held ready, so the first edge after enable is a full
-- half-period away rather than wherever the counter stopped.
sclk_r <= cpol;
cnt <= (others => '0');
in_b <= '0';
elsif derr = '1' then
-- A divisor below two cannot produce an SCLK period at all.
-- Holding the line idle is the safe response: a device sees no
-- clock rather than a malformed one.
sclk_r <= cpol;
elsif in_b = '0' then
-- First half: SCLK is away from idle.
if cnt = 0 then
sclk_r <= not cpol;
a_r <= '1';
end if;
if to_integer(cnt) >= to_integer(h_a) - 1 then
cnt <= (others => '0');
in_b <= '1';
else
cnt <= cnt + 1;
end if;
else
-- Second half: SCLK returns to idle.
if cnt = 0 then
sclk_r <= cpol;
b_r <= '1';
end if;
if to_integer(cnt) >= to_integer(h_b) - 1 then
cnt <= (others => '0');
in_b <= '0';
-- The period is complete at the END of the second half,
-- not at the trailing edge -- a consumer counting trailing
-- edges would start the next bit half a period early.
done_r <= '1';
else
cnt <= cnt + 1;
end if;
end if;
end if;
end process;
end architecture;-- spi_clkdiv_strobe_tb.vhd
--
-- Every interval here is MEASURED from the waveform the divider produces,
-- not inferred from its parameters -- because the parameters are what is
-- under test. The checks then assert relationships that must hold for every
-- divisor: the halves sum to the period, the strobes stay in step, and the
-- first half is never shorter than the second.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity spi_clkdiv_strobe_tb is
end entity;
architecture sim of spi_clkdiv_strobe_tb is
constant DIV_W : positive := 8;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal halt : boolean := false;
signal en : std_logic := '0';
signal div : unsigned(DIV_W - 1 downto 0) := to_unsigned(4, DIV_W);
signal cpol : std_logic := '0';
signal sclk : std_logic;
signal edge_a_stb : std_logic;
signal edge_b_stb : std_logic;
signal bit_done : std_logic;
signal half_a : unsigned(DIV_W - 1 downto 0);
signal half_b : unsigned(DIV_W - 1 downto 0);
signal div_err : std_logic;
signal n_a, n_b, n_done : natural := 0;
signal m_half_a, m_half_b, m_period : natural := 0;
signal idle_wrong : natural := 0;
signal clr_counts : std_logic := '0';
signal errors : natural := 0;
begin
clk <= not clk after 5 ns when not halt else '0';
dut : entity work.spi_clkdiv_strobe
generic map (DIV_W => DIV_W)
port map (
clk => clk, rst_n => rst_n,
en => en, div => div, cpol => cpol,
sclk => sclk, edge_a_stb => edge_a_stb, edge_b_stb => edge_b_stb,
bit_done => bit_done,
half_a => half_a, half_b => half_b, div_err => div_err
);
-- Measure the halves from the strobes themselves: A to B is the first
-- half, B to the next A is the second.
measure : process (clk)
variable since_a, since_b : natural := 0;
variable a_now, b_now : natural := 0;
-- SCLK is a REGISTERED output, so it necessarily takes one clock to
-- follow a change of en or cpol. That one-cycle lag is the property
-- that makes it glitch-free, so the idle check allows for it.
variable en_q, cpol_q : std_logic := '0';
begin
if rising_edge(clk) then
if rst_n = '0' then
n_a <= 0; n_b <= 0; n_done <= 0;
since_a := 0; since_b := 0;
idle_wrong <= 0;
else
if clr_counts = '1' then
n_a <= 0; n_b <= 0; n_done <= 0;
else
if edge_a_stb = '1' then n_a <= n_a + 1; end if;
if edge_b_stb = '1' then n_b <= n_b + 1; end if;
if bit_done = '1' then n_done <= n_done + 1; end if;
end if;
-- Both counters are snapshotted before either is touched.
-- Without that, the A branch's increment runs first and the B
-- branch then reads a count that has already advanced, so
-- every first half measures one cycle long -- which looks
-- exactly like the divider putting the remainder in the wrong
-- half.
a_now := since_a;
b_now := since_b;
if edge_a_stb = '1' then
since_a := 1;
m_half_b <= b_now; -- B .. A is the second half
m_period <= b_now + m_half_a;
else
since_a := a_now + 1;
end if;
if edge_b_stb = '1' then
since_b := 1;
m_half_a <= a_now; -- A .. B is the first half
else
since_b := b_now + 1;
end if;
-- Checked only once en and cpol have both been stable for a
-- cycle, because the output is registered.
if en = '0' and en_q = '0' and cpol = cpol_q and
sclk /= cpol then
idle_wrong <= idle_wrong + 1;
end if;
en_q := en;
cpol_q := cpol;
end if;
end if;
end process;
stim : process
variable errs : natural := 0;
procedure run_div(dv : natural; pol : std_logic; periods : natural) is
begin
wait until falling_edge(clk);
en <= '0';
div <= to_unsigned(dv, DIV_W);
cpol <= pol;
for k in 1 to 3 loop
wait until falling_edge(clk);
end loop;
clr_counts <= '1';
wait until falling_edge(clk);
clr_counts <= '0';
en <= '1';
for k in 1 to dv * periods + dv + 4 loop
wait until falling_edge(clk);
end loop;
en <= '0';
for k in 1 to 3 loop
wait until falling_edge(clk);
end loop;
end procedure;
begin
for k in 0 to 2 loop
wait until falling_edge(clk);
end loop;
rst_n <= '1';
wait until falling_edge(clk);
-- 1. THE IDLE LEVEL, for BOTH polarities -- a divider that only ever
-- rests low passes a CPOL=0 test and fails silently on mode 3.
cpol <= '0';
for k in 1 to 4 loop wait until falling_edge(clk); end loop;
if sclk /= '0' then
report " FAIL: disabled with CPOL=0, SCLK is not low";
errs := errs + 1;
end if;
cpol <= '1';
for k in 1 to 4 loop wait until falling_edge(clk); end loop;
if sclk /= '1' then
report " FAIL: disabled with CPOL=1, SCLK is not high";
errs := errs + 1;
end if;
cpol <= '0';
report " idle: SCLK rests at CPOL for both polarities";
-- 2. AN EVEN DIVISOR. Equal halves, and the period is the divisor.
run_div(4, '0', 6);
if m_half_a /= 2 or m_half_b /= 2 or m_period /= 4 then
report " FAIL: div=4 did not measure 2/2 with a period of 4";
errs := errs + 1;
end if;
report " div=4: halves " & integer'image(m_half_a) & "/" &
integer'image(m_half_b) & ", period " &
integer'image(m_period) & ", A=" & integer'image(n_a) &
" B=" & integer'image(n_b) & " done=" & integer'image(n_done);
-- 3. AN ODD DIVISOR. The halves differ by one and the FIRST is the
-- longer -- the decision the header argues for, checked rather
-- than assumed.
run_div(5, '0', 6);
if m_half_a /= 3 or m_half_b /= 2 or m_period /= 5 then
report " FAIL: div=5 did not measure 3/2 with a period of 5";
errs := errs + 1;
end if;
if m_half_a <= m_half_b then
report " FAIL: on an odd divisor the first half is not the longer";
errs := errs + 1;
end if;
report " div=5: halves " & integer'image(m_half_a) & "/" &
integer'image(m_half_b) &
" -- the launch-to-capture half is the longer one";
-- 4. THE SMALLEST LEGAL DIVISOR toggles every system clock.
run_div(2, '0', 8);
if m_half_a /= 1 or m_half_b /= 1 or m_period /= 2 then
report " FAIL: div=2 did not measure 1/1 with a period of 2";
errs := errs + 1;
end if;
if div_err = '1' then
report " FAIL: div=2 was reported as an error"; errs := errs + 1;
end if;
report " div=2: halves 1/1 -- the fastest a synchronous divider can produce";
-- 5. AN ILLEGAL DIVISOR is reported and the line held idle rather
-- than toggling at some improvised rate.
wait until falling_edge(clk);
div <= to_unsigned(1, DIV_W); cpol <= '0'; en <= '1';
for k in 1 to 8 loop wait until falling_edge(clk); end loop;
if div_err /= '1' then
report " FAIL: div=1 was not reported as illegal"; errs := errs + 1;
end if;
if sclk /= '0' then
report " FAIL: an illegal divisor produced a clock"; errs := errs + 1;
end if;
wait until falling_edge(clk);
div <= to_unsigned(0, DIV_W);
for k in 1 to 8 loop wait until falling_edge(clk); end loop;
if div_err /= '1' or sclk /= '0' then
report " FAIL: div=0 was not held idle and reported";
errs := errs + 1;
end if;
en <= '0'; div <= to_unsigned(4, DIV_W);
for k in 1 to 3 loop wait until falling_edge(clk); end loop;
report " div<2: reported, and the line held idle rather than improvised";
-- 6. STROBE COUNTS. A run does not stop on a period boundary, so the
-- counts are not equal -- they are in STEP. Each period produces A
-- then B then done, so the three differ by at most one and never
-- go out of order. That holds at every cycle rather than only at
-- boundaries.
run_div(6, '0', 10);
if (n_a - n_b) > 1 or n_a < n_b or (n_b - n_done) > 1 or
n_b < n_done then
report " FAIL: the strobe counts are out of step";
errs := errs + 1;
end if;
if n_done < 9 then
report " FAIL: too few periods in a run of ten"; errs := errs + 1;
end if;
report " strobes: A=" & integer'image(n_a) & " B=" &
integer'image(n_b) & " done=" & integer'image(n_done) &
" -- in step, one of each per period";
-- 7. THE SWEEP. For every divisor from 2 to 24, both polarities, the
-- measured halves must match the published ones and sum to the
-- period.
for d in 2 to 24 loop
for k in 0 to 1 loop
if k = 1 then run_div(d, '1', 4); else run_div(d, '0', 4); end if;
if m_period /= d then
report " FAIL: a swept divisor measured the wrong period";
errs := errs + 1;
end if;
if (m_half_a + m_half_b) /= d then
report " FAIL: the halves do not sum to the period";
errs := errs + 1;
end if;
if m_half_a /= to_integer(half_a) or
m_half_b /= to_integer(half_b) then
report " FAIL: the measured halves differ from the published";
errs := errs + 1;
end if;
if m_half_a < m_half_b then
report " FAIL: the first half is shorter than the second";
errs := errs + 1;
end if;
if (n_a - n_b) > 1 or n_a < n_b or (n_b - n_done) > 1 or
n_b < n_done then
report " FAIL: a swept divisor's strobes went out of step";
errs := errs + 1;
end if;
end loop;
end loop;
report " 46 (divisor, polarity) pairs swept: measured halves match the published ones, sum to the period, and the first is never shorter";
-- 8. THE LINE NEVER MOVED WHILE DISABLED, across everything above.
if idle_wrong /= 0 then
report " FAIL: SCLK left the idle level while disabled";
errs := errs + 1;
end if;
report " disabled: 0 cycles on which SCLK left the idle level";
errors <= errs;
if errs = 0 then
report "PASS: SCLK rests at CPOL for both polarities whenever the divider is disabled and never left it once, an even divisor gives equal halves and an odd one gives a longer FIRST half so the launch-to-capture interval is the longer of the two, the smallest legal divisor of two toggles every system clock, a divisor below two is reported and the line held idle rather than improvised, and across 46 divisor and polarity pairs the measured halves match the published ones, sum to the period, and produce exactly one A strobe, one B strobe and one bit_done per period";
else
report "FAIL: " & integer'image(errs) & " error(s)" severity error;
end if;
halt <= true;
wait;
end process;
end architecture;Parity
All three implementations sweep 46 divisor-and-polarity pairs, and in every one the measured halves match the published ones, sum to the period, and the first is never shorter than the second. All three also confirm that SCLK never leaves its idle level while the divider is disabled — across the entire run, not merely at the points where it is checked.
7. Why a Verification Engineer Cares
// Every property here is about the RELATIONSHIP between the pin and the strobes,
// which is the contract the datapath depends on. None of them recomputes the
// halves from `div`: doing that would reproduce the design's own arithmetic and
// agree with it whatever it does.
module spi_clkdiv_strobe_sva #(parameter int DIV_W = 8) (
input logic clk,
input logic rst_n,
input logic en,
input logic cpol,
input logic [DIV_W-1:0] div,
input logic sclk,
input logic edge_a_stb,
input logic edge_b_stb,
input logic bit_done,
input logic [DIV_W-1:0] half_a,
input logic [DIV_W-1:0] half_b,
input logic div_err
);
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
// The two strobes must alternate, strictly. This is the property the
// datapath relies on and the one a counter off-by-one breaks.
logic expect_a;
always_ff @(posedge clk) begin
if (!rst_n) expect_a <= 1'b1;
else if (edge_a_stb) expect_a <= 1'b0;
else if (edge_b_stb) expect_a <= 1'b1;
end
a_alternate_a: assert property (edge_a_stb |-> expect_a);
a_alternate_b: assert property (edge_b_stb |-> !expect_a);
// Never both on one cycle, at any divisor -- including two, where the
// halves are a single cycle each.
a_not_both: assert property (!(edge_a_stb && edge_b_stb));
// A leading strobe means SCLK left idle on the previous edge; a trailing
// strobe means it returned. Stated against the PIN, one cycle back, because
// SCLK is registered.
a_a_means_left: assert property (edge_a_stb |-> sclk != cpol);
a_b_means_returned: assert property (edge_b_stb |-> sclk == cpol);
// The published halves must sum to the divisor, and the first must never be
// the shorter. The second is the odd-divisor decision, and it is the one a
// reviewer is most likely to invert.
a_halves_sum: assert property (div >= 2 |-> (half_a + half_b) == div);
a_first_longer: assert property (div >= 2 |-> half_a >= half_b);
// Disabled means idle, with one cycle of tolerance because SCLK is a
// registered output and therefore cannot follow `en` or `cpol` faster.
a_idle_when_off: assert property (
!en && $past(!en) && $stable(cpol) |-> sclk == cpol
);
// An illegal divisor produces no clock at all, rather than an improvised one.
a_err_no_clock: assert property (div_err |=> $stable(sclk) || !en);
a_err_reported: assert property ((div < 2) == div_err);
// `bit_done` closes a period, so it must follow a trailing strobe and never
// coincide with a leading one.
a_done_after_b: assert property (bit_done |-> !edge_a_stb);
endmodule// The divisor's absolute value is uninteresting; its PARITY and its distance
// from the legal minimum are what exercise different logic. A suite that sweeps
// 8 through 64 in steps of 8 has tested one case eight times.
covergroup cg_clkdiv @(posedge clk);
// Parity is the axis that selects between equal and unequal halves.
parity: coverpoint (div % 2) iff (en) {
bins even = {0};
bins odd = {1};
}
// Distance from the minimum. Two is the fastest legal value and has
// single-cycle halves; one and zero are illegal and must be reported.
magnitude: coverpoint div {
bins illegal_zero = {0};
bins illegal_one = {1};
bins minimum = {2};
bins three = {3};
bins small = {[4:8]};
bins medium = {[9:32]};
bins large = {[33:$]};
}
// Both polarities, because the strobe definitions are relative to idle and
// a design that hard-codes rising and falling passes at CPOL=0 only.
pol: coverpoint cpol { bins low = {0}; bins high = {1}; }
// The enable's transitions matter more than its level: the interesting case
// is re-enabling after a stop, where the counter's reset state decides
// whether the first half is a full one.
en_edge: coverpoint {$past(en), en} {
bins starting = {2'b01};
bins stopping = {2'b10};
bins running = {2'b11};
bins stopped = {2'b00};
}
// Stopping MID-HALF rather than on a boundary: the case that leaves a
// counter in an arbitrary state, and the reason it is cleared.
stop_phase: coverpoint phase_at_stop iff (stopping) {
bins in_first_half = {0};
bins in_second_half = {1};
}
x_parity_pol: cross parity, pol;
x_min_pol: cross magnitude, pol;
endgroup8. Why an FPGA or ASIC Engineer Cares
SCLK is a registered output, which is the single most important sentence in this chapter for implementation. The alternatives are worse in specific ways:
sclk = clk & en a gated clock: needs a clock-gating cell,
a constraint, and a CDC review; glitches if
`en` is not clean
sclk = counter[N] a divided clock: becomes a clock tree, needs
its own constraint, and anything clocked on it
creates a second domain
sclk <= <registered value> a data output that happens to look like a
clock; one domain, no constraints, no cellsThe third is what this design does, and it is only available because nothing in the master is clocked on SCLK. The datapath is clocked on clk and told when the edges happen by the strobes, which is exactly what the strobes are for. A design that clocks its shift register on SCLK has a second clock domain, needs a generated-clock constraint, and cannot use the strobes at all.
The counter is one comparator wide, and the comparison is against a value that changes once per half-period. half_a and half_b are combinational from div, and div comes from the configuration latch of Chapter 13.2, so it is stable for the whole frame. That means the comparator's second input is effectively a constant during operation — no arithmetic on the critical path.
Computing the halves is a shift, not a divide. ceil(div/2) is (div + 1) >> 1 and floor(div/2) is div >> 1. No divider is inferred, and a reviewer who sees / 2 in RTL and reaches for a comment can be pointed at the shift the synthesiser will produce.
Cost. DIV_W flops for the counter, one for the phase bit, four for the registered outputs, one comparator and one incrementer. At DIV_W = 8 that is 13 flops and an 8-bit compare — the smallest block in the master.
9. Failure Signature — A Read That Is Marginal At Every Odd Divisor
Symptom. A sensor read is reliable at divisors 4, 6 and 8, and intermittently returns corrupted data at 5 and 7. Raising the divisor from 5 to 6 fixes it. Lowering from 6 to 5 breaks it again, reproducibly.
What that rules out. A fault that tracks the parity of a parameter is not noise, not a supply problem and not layout — those would track frequency monotonically. Parity means the failing configurations differ from the working ones in something that only odd values do, and in a divider there is exactly one such thing.
What is confusing about it. The SCLK frequency at div=5 is lower than at div=4, and div=4 works. So the failure gets slower-and-worse, which contradicts every intuition about clock rate and sends investigations toward signal integrity. The measured SCLK period is correct at every divisor, so a scope on SCLK alone shows nothing wrong.
The mechanism. The design put the remainder in the second half. At div=4, launch-to-capture is 2 cycles. At div=5 it is still 2 cycles, because the extra cycle went into the other half — and the total period got longer, so the slave's internal logic has more time to prepare but no more time to drive. If 2 cycles was already marginal against the part's output-valid time, div=5 is exactly as marginal as div=4 while appearing to be a safer setting.
Why div=7 also fails and div=6 does not. div=6 gives 3 cycles of launch-to-capture; div=7 gives 3 as well, for the same reason. So the pattern is that odd divisors give the same launch-to-capture interval as the even divisor below them — which means the sequence of margins is 2, 2, 3, 3, 4, 4 rather than 2, 3, 3, 4, 4, 5. Every odd step buys nothing.
How the testbench catches it. By asserting half_a >= half_b at every odd divisor in the sweep, and by measuring both halves from the strobes rather than computing them. A test that only checked the period would pass, because the period is right.
The fix, and the audit it implies. Move the remainder to the first half. Then audit any recorded "this part needs a slower clock than its datasheet claims" notes: the usual cause of such a note is this bug, and the divisor is one step higher than it needs to be everywhere it appears.
10. Common Misconceptions
"The divider should emit launch and capture strobes, since that is what the datapath wants." Then the divider needs cpha, and it has to be re-verified whenever the mode logic changes. The divider publishes facts about the clock; Chapter 13.5 converts them into decisions about the protocol.
"bit_done and the trailing edge are the same event." They are half a period apart. bit_done closes the period at the end of the second half; the trailing edge is at its start. A consumer using the trailing edge as a period boundary starts every bit half a period early.
"An odd divisor is an edge case not worth handling carefully." It is half of all divisors, and getting it wrong costs margin rather than function — which makes it the hardest kind of bug to find, because nothing fails.
"SCLK should be generated by dividing the clock, which is what a divider does." A divided clock becomes a clock tree with its own constraints and creates a second domain for anything that uses it. A registered output that happens to look like a clock stays in one domain and needs no constraints. The difference is whether anything in your design is clocked on it, and in this design nothing is.
"Holding SCLK idle on an illegal divisor loses information; it should toggle as fast as it can." Toggling as fast as it can is a clock the slave will sample, which turns a configuration error into corrupted data. Holding idle produces a state every slave already handles — no clock — and div_err carries the information that would otherwise be lost.
11. Reason It Through
Why is the counter cleared when the divider is disabled rather than frozen?
So that the first edge after re-enabling is a full half-period away from the enable. If the counter keeps whatever value it had when the previous frame ended, the first half of the next frame is shortened by that amount — and the amount depends on the previous frame's length, which makes the resulting timing violation depend on history. Clearing it means every frame's first half is the same length as every other frame's.
What would go wrong if the halves were recomputed from div inside the testbench and compared against the strobes?
Nothing, if the arithmetic were written independently. Everything, if it were copied from the design — which is what happens in practice, because the expression is right there and obviously correct. The reason the testbench measures the halves from the strobes is that a measurement cannot share a bug with the thing it measures.
At a divisor of two, both halves are one cycle. What does that break, and what does it prove?
Nothing, and that is the point: div=2 is the fastest legal setting and it makes every interval degenerate. The leading and trailing strobes land on consecutive cycles, bit_done lands the cycle after the trailing strobe, and any check that assumed a gap between two strobes fails. It is the single most valuable divisor in the sweep because it is where every off-by-one shows.
Why is div_err a combinational function of div rather than a sticky bit?
Because it describes the current configuration, not a past event. A sticky version would stay set after the configuration was corrected, and a driver that fixed its divisor would still see the error. The sticky flags in this module — change_ignored, rx_overrun, rx_trunc — all describe events; div_err, len_err and sel_err all describe states, and the distinction is worth keeping consistent.
A consumer counts trailing edges to know when a bit period has elapsed, and the design works at every divisor above two but fails at two. Explain.
At div=2 the trailing edge is one cycle before the period ends and one cycle after the leading edge, so a consumer counting trailing edges starts the next bit one cycle early — which at div=2 is half the period. At larger divisors the same error is a smaller fraction and usually absorbed by the slave's setup margin, so the bug hides until someone configures the fastest clock. That is the general shape: an off-by-one in a period boundary scales as one over the divisor, so it appears first at the fastest setting, which is the last one anybody tests.
12. Understanding Check
13. Summary
The divider produces SCLK and three strobes — leading, trailing, and period-complete — and deliberately not launch and capture, because that mapping depends on CPHA and a divider that knows the mode must be re-verified when the mode logic changes.
The strobes are defined relative to the idle level, which is how the divider absorbs the whole of CPOL. Everything downstream sees a first and a second edge, so inverting CPOL inverts the pin and nothing else.
At an odd divisor the first half gets the remainder, because in CPHA=1 the launch-to-capture interval is the first half and that is what the slave's output-valid time and the round trip must fit into. The opposite choice gives an odd divisor the same margin as the even divisor below it — a failure that costs margin rather than function, and that presents as "this part needs a slower clock than its datasheet says".
bit_done closes the period at the end of the second half, half a period after the trailing edge. A consumer using the trailing edge as a period boundary starts each bit early, and the error is largest at the fastest divisor.
SCLK is a registered output. Not a gated clock, not a divided clock — a data output that happens to look like one, which keeps the master in a single clock domain and needs no constraints or cells. This is only available because nothing in the design is clocked on SCLK, and that is what the strobes are for.
When disabled, the counter is cleared rather than frozen, so every frame's first half is the same length. A divisor below two holds the line idle and reports, because improvising a clock turns a configuration error into corrupted data.
For verification the assertions are about the relationship between the pin and the strobes, and none recomputes the halves from div. Coverage bins parity and distance from the minimum, because the absolute divisor is uninteresting and a sweep in steps of eight tests one case repeatedly. And the counts of the three strobes are asserted to be in step, not equal, because a run does not end on a period boundary.
14. What Comes Next
The clock runs and its edges are announced. Nothing yet decides what they mean.
Chapter 13.5 — CPOL/CPHA-Aware Edge Control is the whole of the mode logic, and it is smaller than expected: CPOL has already been consumed, so the decision depends on CPHA alone. What is not smaller than expected is the asymmetry between the two phases — one of them needs a bit on MOSI before any clock has moved, and has an edge at the far end that must be ignored.
Continue learning
Related tutorials
- Related topic
SCLK Generation, Period, and Frequency
Where SCLK comes from and what one period buys. Dividing a system clock to a bus clock, why the divisor is an integer and what that costs, and how a period in nanoseconds becomes the budget every later timing parameter is spent from.
- Related topic
Maximum Practical SCLK
The four ceilings on clock rate and which one actually binds: the round-trip budget dominated by the slave's valid time, signal integrity on a shared net, divisor granularity, and the per-slave rate limiter that keeps a mixed bus honest.
- 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.
