Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
f_SCLK = 1 / T_SCLK          T_SCLK = 1 / f_SCLK

Worked, 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 cycles
A system clock running for eight cycles with a divided SCLK below it. A counter runs zero, one, zero, one repeatedly, toggling SCLK each time it reaches one. SCLK is therefore low for two system cycles then high for two, giving an SCLK period of four system-clock cycles, which is a divide by four.one SCLK periodone SCLK periodnext periodnext periodtoggletoggletoggletoggleclkcnt01010101sclkt0t1t2t3t4t5t6t7
Figure 1 — SCLK produced by dividing a system clock by four. The counter reaches its terminal value and toggles SCLK, so one SCLK period spans four system-clock cycles and each half spans two. The divisor is an integer, which is why the reachable frequencies are discrete.

Read 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.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_clkdiv.sv — divide the system clock to SCLK, plus single-cycle edge strobes
   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
   endmodule

The 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.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_clkdiv_tb.sv — measures the period, checks strobe alignment, and checks parking
   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
   endmodule

The 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.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_clkdiv.v — the same divider in Verilog-2001, with a constant function for the width
   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
Azvya Education Pvt. Ltd.VLSI Mentor
spi_clkdiv_tb.v — the same three checks in Verilog-2001
   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
   endmodule

The 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.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_clkdiv.vhd — the same divider in VHDL, with a range-constrained counter
   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;
Azvya Education Pvt. Ltd.VLSI Mentor
spi_clkdiv_tb.vhd — measures the period, checks strobe alignment and parking
   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