SPI · Module 2
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.
Module 1 established that SPI transmits its own timing: the master drives SCLK, and that clock is what makes the exchange work without either side agreeing a rate in advance. Chapter 1.6 then showed that a bit time is a budget — four delays must fit inside it, and raising the clock closes the window until they do not.
This module is the accounting. It starts here, with the thing being budgeted.
What time budget does one SCLK period actually give the system?
That question has two halves, and engineers routinely answer only the first. The arithmetic half — a frequency is one over a period — is trivial. The engineering half is where the answer comes from: a master almost never produces an arbitrary frequency. It divides a clock it already has, by an integer, and that single constraint shapes what rates are reachable, how much of the period each half occupies, and how precisely you can place the edges that later chapters depend on.
1. SCLK Is Derived, Not Chosen
An SPI master lives inside a design that already has a clock — a system clock, a peripheral-bus clock, a PLL output. It does not have an oscillator per interface, and it would be absurd to add one for a link to a sensor.
So SCLK is generated from that existing clock, and the universal mechanism is a counter: count a number of system-clock cycles, toggle an output, repeat. That is a clock divider, and it has one property that dominates everything else in this chapter.
The divisor is an integer. You cannot count 3.7 cycles. So the set of SCLK frequencies a master can produce is not continuous — it is the system clock divided by the integers the divider supports. Ask for 10 MHz from an 80 MHz system clock and you get exactly 10 MHz, because 8 is an integer. Ask for 10 MHz from a 66 MHz system clock and you cannot have it: dividing by 6 gives 11 MHz, dividing by 8 gives 8.25 MHz, and there is nothing in between.
That is not a defect. It is the reason a driver configures SPI with a divider setting rather than a frequency, and the reason the frequency you get is often not the frequency you asked for.
2. Period, Frequency, and the Two Halves
Fix the vocabulary precisely, because the rest of the module spends these quantities.
The period T_SCLK is the time from one rising edge of SCLK to the next. The frequency f_SCLK is its reciprocal:
f_SCLK = 1 / T_SCLK T_SCLK = 1 / f_SCLKWorked, because the numbers matter later: an SCLK of 40 MHz has a period of 25 ns. At 10 MHz the period is 100 ns. At 1 MHz, 1 µs. Those are exact, and they are the only exact things in this module — everything after this chapter is a delay that must fit inside one of them.
The period divides into a high time and a low time, and a datasheet usually constrains them separately (often as t_CH and t_CL, or a minimum high/low pulse width). A divider built by toggling on a half-count produces a nominally symmetric clock — 50% duty — so each half is T_SCLK / 2. At 40 MHz that is 12.5 ns per half.
Hold that number. Chapter 2.3 will show that the interval between a launch edge and the capture edge that follows it is, in the common arrangement, exactly one of those halves — so the half-period, not the period, is usually the budget that runs out first.
Divide by four — one SCLK period spans four system-clock cycles
8 cyclesRead the figure as the divider's contract: SCLK changes only on a system-clock edge, at the instant the counter reaches its terminal value. Every SCLK edge in an SPI master is therefore aligned to the system clock, and that alignment is what makes the launch and capture strobes of Chapter 2.3 buildable at all.
3. What the Divider Costs You
Three consequences follow from integer division, and all three show up in real bring-up.
The achievable frequency is usually below the target. Because you must stay under the device's maximum, you round the divisor up, which rounds the frequency down. From a 100 MHz system clock against a 30 MHz device limit: dividing by 3 gives 33.3 MHz — over the limit, not allowed. Dividing by 4 gives 25 MHz. You lose 17% of the device's rated capability to the granularity of the divider, and no amount of configuration recovers it. The fix, when it matters, is to change the system clock rather than the divider.
Granularity is worst at high speed. The gap between consecutive achievable rates is large when the divisor is small. From 100 MHz, the first few options are 50, 33.3, 25, 20, 16.7 MHz — coarse steps at the top, and progressively finer further down. So the closer you run to a device's ceiling, the more of it you are likely to waste.
Odd divisors break the symmetry. A divide-by-4 built from a half-count of 2 gives 50% duty. A divide-by-5 cannot: the halves must be 2 and 3 system cycles, so one half of every SCLK period is shorter than the other. That matters because a datasheet's minimum high and low times apply to the actual halves, not to the average — and the short half is the one that will violate first. Implementations handle this differently, and some simply restrict the divider to even values to keep the clock symmetric. Knowing which your controller does is worth five minutes with its reference manual.
4. Building the Divider — Three HDLs
Now make it concrete. This is the first RTL of the module, and it is deliberately narrow: the clock source, and nothing else.
Circuit
A free-running counter and a toggle flip-flop. The counter counts system-clock cycles up to a terminal value derived from the divisor; when it reaches that value it resets to zero and flips the SCLK output. Two single-cycle pulses are also produced — one on the system-clock cycle in which SCLK rises, one in which it falls — because later chapters need to act on an SCLK edge from inside the system clock domain.
Registers
Two pieces of state. cnt, the half-period counter, and sclk_q, the clock output itself. SCLK is a register output, not combinational logic — this matters for synthesis and for I/O timing, and §7 returns to it.
Combinational logic
A terminal-count comparison (cnt == HALF-1) producing a tick, and the two edge pulses, which are tick qualified by the current level of sclk_q. Nothing else.
Clock
The system clock, on its rising edge. Note what this means: SCLK is data inside this module, not a clock. It becomes a clock only when it leaves the chip. Treating a divided clock as data for as long as possible is standard practice and is why this design has no derived clock domain inside it.
Enables
en gates the whole thing. With en low the counter holds and SCLK parks at its inactive level — which is what a master does between transfers, and why Chapter 2.5 can talk about the clock being still while CS moves.
Reset
Asynchronous, active-low, clearing the counter and parking SCLK low. Parking low is a choice, not a requirement: the level SCLK rests at between transfers is a configuration item that Module 3 names and derives. This example picks one so it can be concrete, and §7's misconceptions call that out.
Timing
One SCLK period equals DIV system-clock cycles, so f_SCLK = f_clk / DIV. Each half equals DIV/2 cycles. The edge pulses are exactly one system-clock cycle wide and coincide with the SCLK transition they name.
Synthesis
A small counter (a few flip-flops, width set by the divisor), one comparator, one toggle flip-flop, and two AND gates. On any FPGA or standard-cell library this is negligible area and will meet timing at any plausible system-clock rate.
Limitation
This is not an SPI clock engine. It has no notion of a transfer, so it cannot start on a CS assertion, stop after a bit count, or guarantee that a burst contains exactly N periods. It has no mode configuration — Chapter 2.2 explains why "parks low" is only one of two possibilities and Module 3 derives both. And it has no runtime divisor change, which a real controller needs and which requires care to avoid emitting a runt clock pulse. Module 13 builds the real thing.
module spi_clkdiv #(
parameter int DIV = 4 // SCLK period in clk cycles; even, >= 2
) (
input logic clk, // system clock — the only real clock here
input logic rst_n, // asynchronous, active-low
input logic en, // low: counter holds, SCLK parks inactive
output logic sclk, // registered output
output logic sclk_rise, // 1 clk cycle, coincident with SCLK 0->1
output logic sclk_fall // 1 clk cycle, coincident with SCLK 1->0
);
localparam int HALF = DIV / 2;
// $clog2(1) is 0, which would declare a [-1:0] vector — guard the width.
localparam int CW = (HALF <= 1) ? 1 : $clog2(HALF);
logic [CW-1:0] cnt;
logic sclk_q;
logic tick;
assign tick = en && (cnt == CW'(HALF - 1)); // terminal count for one half
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= '0;
sclk_q <= 1'b0; // park low (one choice — see Module 3)
end else if (!en) begin
cnt <= '0; // held, so the next burst starts aligned
sclk_q <= 1'b0;
end else if (tick) begin
cnt <= '0;
sclk_q <= ~sclk_q; // half a period has elapsed
end else begin
cnt <= cnt + 1'b1;
end
end
assign sclk = sclk_q;
assign sclk_rise = tick && ~sclk_q; // this cycle SCLK becomes 1
assign sclk_fall = tick && sclk_q; // this cycle SCLK becomes 0
endmoduleThe testbench measures what the module claims: that an SCLK period really is DIV system-clock cycles, that the two strobes are one cycle wide and land on the transitions they name, and that dropping en parks the clock rather than leaving it mid-period.
module spi_clkdiv_tb;
localparam int DIV = 4;
logic clk = 1'b0, rst_n, en;
logic sclk, sclk_rise, sclk_fall;
logic sclk_d;
int errors = 0;
int rise_count = 0, fall_count = 0;
int last_rise = -1, period = 0;
spi_clkdiv #(.DIV(DIV)) dut (
.clk(clk), .rst_n(rst_n), .en(en),
.sclk(sclk), .sclk_rise(sclk_rise), .sclk_fall(sclk_fall));
always #5 clk = ~clk;
// Independent observation: watch SCLK itself and compare against the strobes.
int cycle = 0;
always @(posedge clk) if (rst_n) begin
sclk_d <= sclk;
cycle <= cycle + 1;
// A strobe must coincide with the transition it names.
if (sclk_rise && sclk === 1'b1) begin
$error("sclk_rise asserted while SCLK already high"); errors++;
end
if (sclk_fall && sclk === 1'b0) begin
$error("sclk_fall asserted while SCLK already low"); errors++;
end
if (sclk && !sclk_d) begin // observed rising edge
rise_count++;
if (last_rise >= 0) begin
period = cycle - last_rise;
if (period != DIV) begin
$error("SCLK period = %0d clk cycles, expected %0d", period, DIV);
errors++;
end
end
last_rise = cycle;
end
if (!sclk && sclk_d) fall_count++;
end
initial begin
rst_n = 1'b0; en = 1'b0;
@(posedge clk); #1;
if (sclk !== 1'b0) begin $error("reset must park SCLK low"); errors++; end
rst_n = 1'b1; en = 1'b1;
repeat (4 * DIV + 2) @(posedge clk); // ~4 SCLK periods
// Parking: dropping en must return SCLK to its inactive level.
en = 1'b0;
@(posedge clk); #1;
if (sclk !== 1'b0) begin
$error("en low must park SCLK low, got %b", sclk); errors++;
end
repeat (DIV) @(posedge clk); #1;
if (sclk !== 1'b0) begin
$error("SCLK moved while parked"); errors++;
end
if (rise_count < 3) begin
$error("only %0d rising edges observed", rise_count); errors++;
end
if (errors == 0)
$display("PASS: period=%0d clk cycles, %0d rises, %0d falls, parks on en=0",
DIV, rise_count, fall_count);
else
$display("FAIL: %0d errors", errors);
$finish;
end
endmoduleThe Verilog form is the same hardware. Two genuine language differences are worth seeing rather than glossing: Verilog-2001 has no $clog2, so the counter width comes from a constant function — a 2001 feature that exists precisely for this; and the strobes use reg/wire typing with always @(*)-free continuous assignments.
module spi_clkdiv #(
parameter DIV = 4 // SCLK period in clk cycles; even, >= 2
) (
input clk,
input rst_n,
input en,
output sclk,
output sclk_rise,
output sclk_fall
);
// Verilog-2001 has no $clog2. A CONSTANT FUNCTION is the standard answer
// and is evaluated at elaboration, so it is legal in a localparam.
function integer clog2;
input integer value;
integer i;
begin
clog2 = 0;
for (i = value - 1; i > 0; i = i >> 1)
clog2 = clog2 + 1;
end
endfunction
localparam HALF = DIV / 2;
localparam CW = (HALF <= 1) ? 1 : clog2(HALF);
reg [CW-1:0] cnt;
reg sclk_q;
wire tick;
assign tick = en && (cnt == (HALF - 1));
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {CW{1'b0}};
sclk_q <= 1'b0;
end else if (!en) begin
cnt <= {CW{1'b0}};
sclk_q <= 1'b0;
end else if (tick) begin
cnt <= {CW{1'b0}};
sclk_q <= ~sclk_q;
end else begin
cnt <= cnt + 1'b1;
end
end
assign sclk = sclk_q;
assign sclk_rise = tick && ~sclk_q;
assign sclk_fall = tick && sclk_q;
endmodule module spi_clkdiv_tb;
parameter DIV = 4;
reg clk = 1'b0;
reg rst_n, en;
wire sclk, sclk_rise, sclk_fall;
reg sclk_d;
integer errors = 0, rise_count = 0, fall_count = 0;
integer last_rise = -1, period = 0, cycle = 0, i;
spi_clkdiv #(.DIV(DIV)) dut (
.clk(clk), .rst_n(rst_n), .en(en),
.sclk(sclk), .sclk_rise(sclk_rise), .sclk_fall(sclk_fall));
always #5 clk = ~clk;
always @(posedge clk) if (rst_n) begin
sclk_d <= sclk;
cycle <= cycle + 1;
if (sclk_rise && sclk === 1'b1) begin
$display("ERROR sclk_rise asserted while SCLK already high");
errors = errors + 1;
end
if (sclk_fall && sclk === 1'b0) begin
$display("ERROR sclk_fall asserted while SCLK already low");
errors = errors + 1;
end
if (sclk && !sclk_d) begin
rise_count = rise_count + 1;
if (last_rise >= 0) begin
period = cycle - last_rise;
if (period != DIV) begin
$display("ERROR SCLK period = %0d clk cycles, expected %0d", period, DIV);
errors = errors + 1;
end
end
last_rise = cycle;
end
if (!sclk && sclk_d) fall_count = fall_count + 1;
end
initial begin
rst_n = 1'b0; en = 1'b0;
@(posedge clk); #1;
if (sclk !== 1'b0) begin
$display("ERROR reset must park SCLK low"); errors = errors + 1;
end
rst_n = 1'b1; en = 1'b1;
for (i = 0; i < 4 * DIV + 2; i = i + 1) @(posedge clk);
en = 1'b0;
@(posedge clk); #1;
if (sclk !== 1'b0) begin
$display("ERROR en low must park SCLK low, got %b", sclk);
errors = errors + 1;
end
for (i = 0; i < DIV; i = i + 1) @(posedge clk);
#1;
if (sclk !== 1'b0) begin
$display("ERROR SCLK moved while parked"); errors = errors + 1;
end
if (rise_count < 3) begin
$display("ERROR only %0d rising edges observed", rise_count);
errors = errors + 1;
end
if (errors == 0)
$display("PASS: period=%0d clk cycles, %0d rises, %0d falls, parks on en=0",
DIV, rise_count, fall_count);
else
$display("FAIL: %0d errors", errors);
$finish;
end
endmoduleThe VHDL form sidesteps the width question entirely, which is the idiomatic difference worth noticing: an integer range 0 to HALF-1 counter tells the tool the exact legal range, so the synthesiser derives the width and the range is checked in simulation. There is no clog2 to write and no off-by-one width bug to make.
library ieee;
use ieee.std_logic_1164.all;
entity spi_clkdiv is
generic (
DIV : positive := 4 -- SCLK period in clk cycles; even, >= 2
);
port (
clk : in std_logic; -- system clock — the only real clock here
rst_n : in std_logic; -- asynchronous, active-low
en : in std_logic;
sclk : out std_logic;
sclk_rise : out std_logic;
sclk_fall : out std_logic
);
end entity spi_clkdiv;
architecture rtl of spi_clkdiv is
constant HALF : positive := DIV / 2;
-- A range-constrained integer needs no clog2: the tool infers the width
-- and simulation range-checks every assignment.
signal cnt : integer range 0 to HALF - 1 := 0;
signal sclk_q : std_logic := '0';
signal tick : std_logic;
begin
tick <= '1' when (en = '1' and cnt = HALF - 1) else '0';
div_proc : process (clk, rst_n)
begin
if rst_n = '0' then
cnt <= 0;
sclk_q <= '0'; -- park low (one choice — see Module 3)
elsif rising_edge(clk) then
if en = '0' then
cnt <= 0;
sclk_q <= '0';
elsif tick = '1' then
cnt <= 0;
sclk_q <= not sclk_q; -- half a period has elapsed
else
cnt <= cnt + 1;
end if;
end if;
end process div_proc;
sclk <= sclk_q;
sclk_rise <= tick and not sclk_q;
sclk_fall <= tick and sclk_q;
end architecture rtl; library ieee;
use ieee.std_logic_1164.all;
entity spi_clkdiv_tb is
end entity spi_clkdiv_tb;
architecture sim of spi_clkdiv_tb is
constant DIV : positive := 4;
constant TP : time := 10 ns;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal en : std_logic := '0';
signal sclk : std_logic;
signal sclk_rise : std_logic;
signal sclk_fall : std_logic;
signal done : boolean := false;
signal errors : natural := 0;
begin
clk <= not clk after TP/2 when not done else '0';
dut : entity work.spi_clkdiv
generic map (DIV => DIV)
port map (clk => clk, rst_n => rst_n, en => en,
sclk => sclk, sclk_rise => sclk_rise, sclk_fall => sclk_fall);
-- Independent observer: measure the period and check strobe alignment.
observe : process (clk)
variable sclk_d : std_logic := '0';
variable cycle : natural := 0;
variable last_rise : integer := -1;
variable period : integer;
begin
if rising_edge(clk) and rst_n = '1' then
cycle := cycle + 1;
if sclk_rise = '1' and sclk = '1' then
report "sclk_rise asserted while SCLK already high" severity error;
errors <= errors + 1;
end if;
if sclk_fall = '1' and sclk = '0' then
report "sclk_fall asserted while SCLK already low" severity error;
errors <= errors + 1;
end if;
if sclk = '1' and sclk_d = '0' then
if last_rise >= 0 then
period := cycle - last_rise;
if period /= DIV then
report "SCLK period = " & integer'image(period) &
" clk cycles, expected " & integer'image(DIV)
severity error;
errors <= errors + 1;
end if;
end if;
last_rise := cycle;
end if;
sclk_d := sclk;
end if;
end process observe;
stim : process
variable errs : natural := 0;
begin
wait until rising_edge(clk); wait for 1 ns;
if sclk /= '0' then
report "reset must park SCLK low" severity error; errs := errs + 1;
end if;
rst_n <= '1'; en <= '1';
for i in 0 to 4 * DIV + 1 loop
wait until rising_edge(clk);
end loop;
-- Parking behaviour.
en <= '0';
wait until rising_edge(clk); wait for 1 ns;
if sclk /= '0' then
report "en low must park SCLK low" severity error; errs := errs + 1;
end if;
for i in 0 to DIV - 1 loop
wait until rising_edge(clk);
end loop;
wait for 1 ns;
if sclk /= '0' then
report "SCLK moved while parked" severity error; errs := errs + 1;
end if;
if errs = 0 and errors = 0 then
report "PASS: period, strobe alignment and parking all correct" severity note;
else
report "FAIL" severity error;
end if;
done <= true;
wait;
end process stim;
end architecture sim;What the three agree on, and where they differ
All three infer the same circuit: a half-period counter, a toggle flip-flop driving a registered SCLK, an enable that holds and parks, an asynchronous active-low reset, and two single-cycle strobes derived from the terminal count and the current SCLK level. Divisor, reset polarity, park level and strobe semantics are identical across the three.
The differences are instructive rather than cosmetic. SystemVerilog has $clog2 built in and always_ff to declare intent to the tool. Verilog-2001 has neither, so the width comes from a constant function — worth knowing because it is the standard 2001 idiom and appears in a great deal of production Verilog. VHDL avoids the question altogether with a range-constrained integer, which also gives simulation-time range checking that neither Verilog dialect provides; the trade is that you must think in ranges rather than in bit widths.
5. Choosing a Divisor — a Worked Example
Put the arithmetic to work on a realistic decision. The numbers below are hypothetical, chosen to make the reasoning visible; they are not taken from any device.
A design has a 100 MHz system clock. A peripheral's datasheet gives a maximum SCLK of 30 MHz, a minimum clock high time of 12 ns and a minimum clock low time of 12 ns.
Which divisors are legal on frequency alone? f_SCLK = 100 / DIV MHz, so DIV = 2 gives 50 MHz and DIV = 3 gives 33.3 MHz — both above the 30 MHz ceiling and therefore excluded. DIV = 4 gives 25 MHz. That is the fastest legal choice by frequency.
Does it satisfy the high and low times? At 25 MHz the period is 40 ns, and an even divisor gives symmetric halves of 20 ns each. Both exceed the 12 ns minimums comfortably, so DIV = 4 is legal on all three constraints.
What did the granularity cost? The device would have accepted 30 MHz; you are running at 25. Roughly 17% of its capability is unreachable from a 100 MHz system clock, and no register setting recovers it. If that throughput mattered, the lever is the system clock — from 120 MHz, DIV = 4 gives exactly 30 MHz.
Why check the high/low times separately at all, if the frequency passed? Because with an odd divisor they can fail independently. Suppose the design instead had a 70 MHz system clock and used DIV = 3 for 23.3 MHz — comfortably under the ceiling. The halves would be 2 and 1 system-clock cycles: about 28.6 ns and 14.3 ns. Still legal here, but the short half is now close to the 12 ns floor, and a slower system clock or a tighter device would break it while the frequency still looked fine. Frequency compliance does not imply pulse-width compliance, and that is exactly the trap Chapter 2.4 generalises.
6. Why a Verification Engineer Cares
Clock generation is where a testbench can check something concrete and cheap, and it is worth doing before any data-level checking exists.
Measure the period, do not assume it. A monitor that timestamps consecutive SCLK rising edges and compares the interval against the configured divisor catches a whole family of bugs — a divider off by one, a runt pulse on a divisor change, a clock that free-runs when it should be parked — none of which produce wrong data in simulation, because simulation has no notion of a period being too short. This is the first thing a timing-aware monitor should do, and Chapter 2.6 develops it into a general observation strategy.
Check the pulse widths separately. As §5 showed, a correct average frequency can hide an asymmetric clock. Measuring high time and low time independently is a different check from measuring the period, and it is the one that catches odd-divisor problems.
Be precise about what a simulation check proves. A testbench measuring "40 ns between edges" is measuring simulation time, which reflects the RTL's cycle behaviour and the testbench's clock period — not the real board. It proves the divider counts correctly. It says nothing about rise times, duty distortion through an I/O buffer, or the clock quality at the far end of a trace. Keeping those layers apart is the discipline Chapter 2.7 insists on.
7. Why an FPGA or ASIC Engineer Cares
Three implementation consequences, and the first is the one that gets designs into trouble.
A divided clock is not a clock — keep it that way. The design above produces SCLK as a registered data output, and nothing inside the module is clocked by it. That is deliberate and it is the standard approach: routing a divided signal onto a clock network creates a derived clock domain, consumes a clock resource, and requires its own constraints and clock-domain analysis. As long as SCLK is data that happens to leave through a pin, the entire master lives in one clock domain. Designs that clock their shift logic on SCLK have made a different architectural choice with real consequences — one Module 15 examines properly, and one that is genuinely unavoidable for an SPI slave, which does not own the clock at all.
Drive SCLK from an I/O register. Because SCLK is an output whose edge position matters to everything downstream, registering it in the I/O block rather than in the fabric makes the pin-to-edge delay short and, more importantly, predictable across builds. The same argument applies to MOSI, and Chapter 2.7 makes it again for the input side, where it matters most.
The output path needs a constraint. The delay from the internal register to the pin is part of the budget the peripheral sees, and it is declared, not discovered — an output delay constraint describing what the board and the device require. Without it the tool has no obligation on that path and the result varies between builds. Module 15 covers writing them.
For ASIC work the same reasoning applies with different vocabulary: the pad and its driver contribute delay that belongs in the interface budget, and the I/O timing is constrained at the boundary rather than inferred. The principle is identical — the clock's edge position at the pin is what the rest of the system consumes.
8. Failure Signature — SCLK at Half the Expected Frequency
A signature worth recognising immediately, because its cause is nearly always the same.
Symptom. The bus works, transfers complete correctly, but SCLK measures exactly half the configured frequency — or exactly twice the expected period.
Candidate mechanisms. Three are plausible. The divisor is being interpreted as a half-period count where the driver supplied a full-period count, or vice versa. The toggle is happening once per full count rather than once per half. Or the system clock feeding the divider is not the frequency the driver believes it is — a PLL setting, a clock-source mux, or a default the boot code never changed.
The discriminating observation. Measure the system clock and SCLK at the same time. If the ratio is twice the configured divisor, the fault is in the divider's interpretation of its parameter — a software/hardware contract mismatch, fixed by a factor of two in one place. If the ratio is exactly the configured divisor and SCLK is still half of what you expected, the divider is correct and the system clock is half what you assumed, which is a clocking-infrastructure problem with nothing to do with SPI.
Why the exactness is the clue. A factor of exactly two, stable across runs and temperatures, is a counting error. Analogue and marginal problems do not produce exact integer ratios — they produce intermittency, data dependence and drift. Whenever a frequency is wrong by a clean integer factor, look at arithmetic and configuration before touching the board.
9. Common Misconceptions
10. Reason It Through
Work this before reading the answers.
A design runs an SPI flash from a 50 MHz system clock. The flash is rated to 20 MHz. Firmware configures a divisor of 2, measures SCLK at 25 MHz, and observes that reads are intermittently corrupt. An engineer proposes moving to a divisor of 4 "to be safe."
Is the proposed fix correct? It will almost certainly make the symptom disappear, and it is still the wrong first response — because it does not establish what was wrong. Divisor 2 gives 25 MHz against a 20 MHz ceiling, so the clock was illegal and that alone explains the corruption. But "it stopped failing" is not the same as "the fault was understood," and the same reflex applied to a genuinely marginal board hides problems rather than fixing them.
What is the correct reasoning? Start from the constraint, not the symptom. The largest legal frequency is 20 MHz; the reachable set from 50 MHz is 25, 16.7, 12.5, 10 MHz and so on. The fastest legal choice is therefore DIV = 4 at 12.5 MHz, not because it is "safe" but because 25 MHz violates the device and nothing between 25 and 12.5 is reachable. Divisor 3 would give 16.7 MHz, which is legal and faster — and whether it is available depends on whether the controller permits odd divisors and whether the resulting asymmetric halves satisfy the device's minimum high and low times.
What does the granularity cost here? The device supports 20 MHz; the best symmetric option is 12.5 MHz, which leaves 37% of its capability unreachable. That is a large loss, and it is a system clock problem rather than an SPI problem — from a 40 MHz system clock, DIV = 2 would give exactly 20 MHz.
What should have prevented this? A divisor computed from the device's maximum rather than chosen by hand. The calculation is DIV_min = ceil(f_clk / f_max), evaluated at driver initialisation, with the result checked against the pulse-width minimums. Hard-coding a divisor that happened to work on a previous board is how this class of bug reaches production.
A caution about the intermittency. Corruption that comes and goes is consistent with an over-clocked device, but it is also the signature of a round-trip timing shortfall (Chapter 2.7) and of contention on a shared return line (Chapter 1.2). Here the illegal frequency is sufficient to explain it, and the discriminating test is simple: if the link is solid at a legal frequency and fails at every illegal one, the clock was the mechanism. If it still misbehaves at a legal frequency, the frequency was a coincidence and the real fault is still in the system.
11. Understanding Check
12. Summary
SCLK is derived, not chosen. A master divides a clock it already has, by an integer, so the reachable frequencies are f_clk / DIV and nothing between them. A driver therefore configures a divisor, and the frequency obtained is usually the largest reachable one below the device's ceiling — which at high speed can leave a substantial fraction of the device's capability unreachable, recoverable only by changing the system clock.
The period is T = 1/f, and the numbers are worth carrying: 40 MHz is 25 ns, with symmetric halves of 12.5 ns. The half-period is usually the operative budget, because the interval between launching a bit and capturing it is half a period in the common arrangement — the point Chapter 2.3 establishes and every later chapter spends against.
Frequency is not the only constraint. Minimum clock high and low times apply to the real halves, and an odd divisor makes them unequal, so a legal average frequency can hide an illegal short pulse. Check the halves.
In RTL the divider is a counter and a toggle flip-flop, with SCLK emitted as a registered data output and single-cycle strobes marking each SCLK transition from inside the system clock domain. Keeping SCLK as data rather than as a clock keeps the master in one domain — a choice with real consequences that later modules examine, and one a slave does not get to make.
13. What Comes Next
You now have a clock and a period. The next question is what the edges of that clock mean. Chapter 2.2 — Leading and Trailing Edges shows why "rising" and "falling" are the wrong vocabulary for describing an SPI transfer, and introduces the terms that stay correct regardless of what level the clock rests at between transfers — the vocabulary Chapter 2.3 needs in order to talk about which edge does what, and that Module 3 will eventually derive the four modes from.
Browse the path on the SPI curriculum index, or revisit Electrical and Board-Level Limits for the reason a period is a budget at all.
Continue learning
Related tutorials
- Related topic
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.
- Related topic
The Shift-Register Mental Model
The hardware underneath every SPI transfer: two shift registers wired into a ring, one bit leaving each end and one arriving on every enabled edge. Traced cycle by cycle, built in Verilog, SystemVerilog and VHDL, and separated from the meaning a device layers on top.
- 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
CS-to-SCLK and SCLK-to-CS Timing
Chip select has timing requirements of its own: the lead before the first clock edge, the lag after the last, and the minimum deselect between transactions. Why violating them breaks a transfer whose every SCLK edge was correct.
