SPI · Module 2
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.
Chapter 2.2 gave each bit time two named edges — leading and trailing — without saying what either one does. This chapter assigns the jobs, and the assignment is the single most important agreement in SPI.
Which side changes the data, which side captures it, and why must those be different edges?
Everything in Module 1 depended on this without stating it. Chapter 1.3's ring shifted "on an enabled edge" and deliberately deferred which one. Chapter 1.6 spoke of "the window the master allows between the slave's launch and its own capture" without defining the window's ends. Both of those are now on the table, and the answer is short enough to state immediately:
One edge of each bit time launches; the other samples. They are always different edges, and the interval between them is half a period.
The rest of the chapter is why that arrangement is forced rather than chosen, and what it costs when it is got wrong.
1. The Problem With One Edge
Suppose launch and sample happened on the same edge. The transmitter changes its output at that instant, and the receiver captures its input at that same instant.
This fails, and it fails for a reason that has nothing to do with SPI. A flip-flop does not sample a value at an idealised point in time; it requires its input to be stable for an interval before the capturing edge and for a short interval after it. A signal that is changing at exactly the capture moment violates that requirement by construction. The captured value is then not merely unpredictable between old and new — it can leave the flip-flop in a metastable state, resolving to some value after an unbounded delay.
So a single shared edge is not a design that occasionally gets the wrong bit. It is a design with no defined behaviour at all. Chapter 2.4 makes that requirement quantitative; here it is enough that the requirement exists.
The fix is to separate the two events in time, and a two-edge clock hands you the separation for free: use one edge for launching and the other for capturing, and the data is guaranteed to have been stable since the previous edge.
2. The Contract
State it precisely.
The launch edge is the edge on which a transmitting device places a new bit on its output line. Before that edge the line carries the previous bit; after it, and after the device's own output delay, the line carries the new one.
The sample edge is the edge on which a receiving device captures whatever its input line is carrying.
They are different edges of the same bit time. If the launch edge is the leading edge, the sample edge is the trailing edge, and vice versa. Which way round is a configuration item — one that Module 3 names, combines with the resting level from Chapter 2.2, and turns into the four standard mode numbers. Module 2's job is the mechanism, not the naming.
Both directions use the same assignment. The master launches on MOSI and the slave samples it; the slave launches on MISO and the master samples it. All four of those events use the same two edges — launches together on one, samples together on the other. This is a direct consequence of Chapter 1.3's ring: one register per end, one shift per bit time, so a single pair of instants serves both directions.
Launch on the leading edge, sample on the trailing edge
8 cyclesRead the figure as a repeating two-step. At column 0 the clock leaves its resting level — the leading edge — and the master drives b3 onto MOSI. At column 1 the clock returns — the trailing edge — and the receiver samples. Between those two instants the line has been at its new value for a full half-period, which is precisely the margin the next chapter spends.
Then it repeats: launch at column 2, sample at column 3, and so on for every bit of the transfer. The marked pair in the figure is one instance of a pattern that holds for every bit time.
3. Why the Budget Is a Half Period, Not a Period
This is the most commonly mis-stated number in SPI, so it is worth deriving rather than asserting.
A bit occupies a full period — it is placed at one edge and remains on the line until the corresponding edge of the next bit time. So the bit is a period long. But the interval a receiver actually has, between the moment the data becomes valid and the moment it must be stable for capture, is the gap between the launch edge and the sample edge, and those are adjacent edges. That gap is half a period.
Concretely, using Chapter 2.1's numbers: at 40 MHz the period is 25 ns and the budget is 12.5 ns. At 10 MHz, 50 ns. Every delay that Chapter 2.6 and Chapter 2.7 introduce — the device's output delay, the propagation across the board and back, the receiver's setup requirement — must fit inside that half, not inside the period.
Engineers who budget against the full period conclude that a link has twice the margin it really has. That error is benign at low frequency and fatal near the limit, which is exactly where the calculation is being done.
4. Mapping Physical Edges to Roles — Three HDLs
Chapter 2.1's divider produced two physical pulses per period: sclk_rise and sclk_fall. Chapter 2.2 explained that which of those is leading depends on the resting level. This chapter adds that which logical edge samples is a second configuration. Put the three facts together and you get a small, precise piece of hardware.
Circuit
Two levels of two-input selection. The first maps physical rise/fall pulses onto logical leading/trailing pulses according to the resting level. The second maps logical leading/trailing onto the launch and sample roles according to the edge-role configuration. The output is two single-cycle strobes, and the design makes it structurally impossible for them to coincide.
Registers
None. This module holds no state, and that is the point worth explaining rather than apologising for. The state lives elsewhere: the divider (Chapter 2.1) holds the phase of the clock, and the shift register (Chapter 1.3) holds the data. This block is pure routing — it decides which pulse means what. Keeping it combinational means it adds no latency between the divider's strobe and the datapath's response, which matters because both must occur in the same system-clock cycle as the SCLK transition they describe.
Combinational logic
Four two-input multiplexers, or equivalently a handful of AND/OR gates. That is the entire design.
Clock
None internally. The strobes it consumes and produces are all qualified for the system clock, in the same cycle — this module sits between two clocked blocks without being clocked itself.
Enables
sclk_rise and sclk_fall are themselves the enables, arriving from the divider one system-clock cycle wide. If the divider is disabled, neither pulses and neither strobe fires, so a parked clock produces no launches and no samples. That falls out of the structure rather than needing a separate gate.
Reset
None required, because there is no state. The downstream consumers reset; this block simply stops pulsing when its inputs stop.
Timing
The invariant is launch_stb and sample_stb are never asserted in the same system-clock cycle. Because they are derived from complementary selections of the same two mutually exclusive inputs, this holds by construction — there is no configuration of the inputs that makes both fire.
Synthesis
A few gates. Negligible.
Limitation
This routes edges; it does not know what a transfer is. It has no bit counter, so it cannot stop after the configured width; no CS awareness, so it pulses whenever the divider runs; and no handling of the first bit, which under one of the two edge-role configurations must be launched before the first clock edge — a genuinely awkward case that Module 3 explains and Module 14 implements. It also deliberately does not name its two configuration bits, because naming them and enumerating their four combinations is Module 3's chapter, not this one.
module spi_edge_roles (
// Physical edge strobes from the divider (Chapter 2.1) — mutually
// exclusive, one system-clock cycle wide.
input logic sclk_rise,
input logic sclk_fall,
// Configuration. Module 3 names these two bits and derives the four
// standard mode numbers from them; here we need only the mechanism.
input logic idle_high, // 1: SCLK rests high (Chapter 2.2)
input logic sample_on_trailing, // 1: capture on the trailing edge
// Logical strobes for the datapath.
output logic launch_stb, // drive the next bit onto the line
output logic sample_stb // capture whatever the line carries
);
logic leading_stb, trailing_stb;
// Chapter 2.2: the leading edge takes the clock AWAY from its rest level.
assign leading_stb = idle_high ? sclk_fall : sclk_rise;
assign trailing_stb = idle_high ? sclk_rise : sclk_fall;
// The roles are COMPLEMENTARY selections of the same two inputs, so the
// two strobes can never coincide — the separation is structural.
assign sample_stb = sample_on_trailing ? trailing_stb : leading_stb;
assign launch_stb = sample_on_trailing ? leading_stb : trailing_stb;
endmoduleThe invariant the module exists to guarantee is worth asserting, because a future "optimisation" that derives the two strobes independently would break it silently:
// If both strobes ever fire in the same cycle, a transmitter would be
// changing the line at the instant a receiver captures it — the exact
// condition §1 showed has no defined behaviour.
a_roles_exclusive : assert property (
@(posedge clk) disable iff (!rst_n) !(launch_stb && sample_stb)
) else $error("launch and sample strobes coincided");Be precise about its scope. It proves that this design never issues both instructions at once, which is a real property and catches a genuine class of refactoring mistake. It proves nothing about the analogue separation of the two events at a device's pins: that depends on the clock's actual edge positions, on propagation, and on the receiver's setup requirement, none of which a cycle-based assertion can observe. Confusing "the strobes were in different simulation cycles" with "the data was stable at the far end" is the error Chapter 2.7 is written to prevent.
The testbench sweeps every configuration rather than the one the author had in mind, and checks both the mapping and the mutual exclusion.
module spi_edge_roles_tb;
logic sclk_rise, sclk_fall, idle_high, sample_on_trailing;
logic launch_stb, sample_stb;
int errors = 0;
spi_edge_roles dut (
.sclk_rise(sclk_rise), .sclk_fall(sclk_fall),
.idle_high(idle_high), .sample_on_trailing(sample_on_trailing),
.launch_stb(launch_stb), .sample_stb(sample_stb));
// Apply one physical edge and check which role it played.
task automatic check(input bit rise, input bit exp_launch, input bit exp_sample,
input string what);
sclk_rise = rise; sclk_fall = !rise; #1;
if (launch_stb !== exp_launch || sample_stb !== exp_sample) begin
$error("%s: launch=%b sample=%b expected %b/%b",
what, launch_stb, sample_stb, exp_launch, exp_sample);
errors++;
end
if (launch_stb && sample_stb) begin
$error("%s: both strobes asserted", what); errors++;
end
sclk_rise = 1'b0; sclk_fall = 1'b0; #1;
endtask
initial begin
sclk_rise = 1'b0; sclk_fall = 1'b0;
// rests low => leading is RISING, trailing is FALLING
idle_high = 1'b0; sample_on_trailing = 1'b1; // sample trailing
check(1'b1, 1'b1, 1'b0, "idle_low/sample_trailing: rising is leading -> launch");
check(1'b0, 1'b0, 1'b1, "idle_low/sample_trailing: falling is trailing -> sample");
idle_high = 1'b0; sample_on_trailing = 1'b0; // sample leading
check(1'b1, 1'b0, 1'b1, "idle_low/sample_leading: rising is leading -> sample");
check(1'b0, 1'b1, 1'b0, "idle_low/sample_leading: falling is trailing -> launch");
// rests high => leading is FALLING, trailing is RISING
idle_high = 1'b1; sample_on_trailing = 1'b1;
check(1'b0, 1'b1, 1'b0, "idle_high/sample_trailing: falling is leading -> launch");
check(1'b1, 1'b0, 1'b1, "idle_high/sample_trailing: rising is trailing -> sample");
idle_high = 1'b1; sample_on_trailing = 1'b0;
check(1'b0, 1'b0, 1'b1, "idle_high/sample_leading: falling is leading -> sample");
check(1'b1, 1'b1, 1'b0, "idle_high/sample_leading: rising is trailing -> launch");
// No physical edge must produce no strobe, in every configuration.
for (int cfg = 0; cfg < 4; cfg++) begin
{idle_high, sample_on_trailing} = cfg[1:0];
sclk_rise = 1'b0; sclk_fall = 1'b0; #1;
if (launch_stb || sample_stb) begin
$error("cfg %0d: strobe asserted with no SCLK edge", cfg); errors++;
end
end
if (errors == 0) $display("PASS: all 4 configurations map correctly, strobes exclusive");
else $display("FAIL: %0d errors", errors);
$finish;
end
endmodule module spi_edge_roles (
input sclk_rise,
input sclk_fall,
input idle_high,
input sample_on_trailing,
output launch_stb,
output sample_stb
);
wire leading_stb;
wire trailing_stb;
assign leading_stb = idle_high ? sclk_fall : sclk_rise;
assign trailing_stb = idle_high ? sclk_rise : sclk_fall;
assign sample_stb = sample_on_trailing ? trailing_stb : leading_stb;
assign launch_stb = sample_on_trailing ? leading_stb : trailing_stb;
endmodule module spi_edge_roles_tb;
reg sclk_rise, sclk_fall, idle_high, sample_on_trailing;
wire launch_stb, sample_stb;
integer errors = 0, cfg;
spi_edge_roles dut (
.sclk_rise(sclk_rise), .sclk_fall(sclk_fall),
.idle_high(idle_high), .sample_on_trailing(sample_on_trailing),
.launch_stb(launch_stb), .sample_stb(sample_stb));
// Verilog-2001 tasks are static; this one uses no local storage across
// calls, so a plain task is safe here.
task check;
input rise;
input exp_launch;
input exp_sample;
input [255:0] what;
begin
sclk_rise = rise; sclk_fall = !rise; #1;
if (launch_stb !== exp_launch || sample_stb !== exp_sample) begin
$display("ERROR %0s: launch=%b sample=%b expected %b/%b",
what, launch_stb, sample_stb, exp_launch, exp_sample);
errors = errors + 1;
end
if (launch_stb && sample_stb) begin
$display("ERROR %0s: both strobes asserted", what);
errors = errors + 1;
end
sclk_rise = 1'b0; sclk_fall = 1'b0; #1;
end
endtask
initial begin
sclk_rise = 1'b0; sclk_fall = 1'b0;
idle_high = 1'b0; sample_on_trailing = 1'b1;
check(1'b1, 1'b1, 1'b0, "idle_low/sample_trailing rising");
check(1'b0, 1'b0, 1'b1, "idle_low/sample_trailing falling");
idle_high = 1'b0; sample_on_trailing = 1'b0;
check(1'b1, 1'b0, 1'b1, "idle_low/sample_leading rising");
check(1'b0, 1'b1, 1'b0, "idle_low/sample_leading falling");
idle_high = 1'b1; sample_on_trailing = 1'b1;
check(1'b0, 1'b1, 1'b0, "idle_high/sample_trailing falling");
check(1'b1, 1'b0, 1'b1, "idle_high/sample_trailing rising");
idle_high = 1'b1; sample_on_trailing = 1'b0;
check(1'b0, 1'b0, 1'b1, "idle_high/sample_leading falling");
check(1'b1, 1'b1, 1'b0, "idle_high/sample_leading rising");
for (cfg = 0; cfg < 4; cfg = cfg + 1) begin
idle_high = cfg[1];
sample_on_trailing = cfg[0];
sclk_rise = 1'b0; sclk_fall = 1'b0; #1;
if (launch_stb || sample_stb) begin
$display("ERROR cfg %0d: strobe asserted with no SCLK edge", cfg);
errors = errors + 1;
end
end
if (errors == 0) $display("PASS: all 4 configurations map correctly, strobes exclusive");
else $display("FAIL: %0d errors", errors);
$finish;
end
endmodule library ieee;
use ieee.std_logic_1164.all;
entity spi_edge_roles is
port (
-- Physical edge strobes from the divider (Chapter 2.1).
sclk_rise : in std_logic;
sclk_fall : in std_logic;
-- Configuration; Module 3 names these bits and derives the modes.
idle_high : in std_logic; -- '1': SCLK rests high
sample_on_trailing : in std_logic; -- '1': capture on trailing edge
-- Logical strobes for the datapath.
launch_stb : out std_logic;
sample_stb : out std_logic
);
end entity spi_edge_roles;
architecture rtl of spi_edge_roles is
signal leading_stb : std_logic;
signal trailing_stb : std_logic;
begin
-- The leading edge takes SCLK away from its resting level.
leading_stb <= sclk_fall when idle_high = '1' else sclk_rise;
trailing_stb <= sclk_rise when idle_high = '1' else sclk_fall;
-- Complementary selection: the two strobes cannot coincide.
sample_stb <= trailing_stb when sample_on_trailing = '1' else leading_stb;
launch_stb <= leading_stb when sample_on_trailing = '1' else trailing_stb;
end architecture rtl; library ieee;
use ieee.std_logic_1164.all;
entity spi_edge_roles_tb is
end entity spi_edge_roles_tb;
architecture sim of spi_edge_roles_tb is
signal sclk_rise : std_logic := '0';
signal sclk_fall : std_logic := '0';
signal idle_high : std_logic := '0';
signal sample_on_trailing : std_logic := '0';
signal launch_stb : std_logic;
signal sample_stb : std_logic;
begin
dut : entity work.spi_edge_roles
port map (sclk_rise => sclk_rise, sclk_fall => sclk_fall,
idle_high => idle_high, sample_on_trailing => sample_on_trailing,
launch_stb => launch_stb, sample_stb => sample_stb);
stim : process
variable errors : natural := 0;
procedure check (rise : in std_logic;
exp_launch : in std_logic;
exp_sample : in std_logic;
what : in string) is
begin
sclk_rise <= rise;
sclk_fall <= not rise;
wait for 1 ns;
if launch_stb /= exp_launch or sample_stb /= exp_sample then
report what & ": wrong role mapping" severity error;
errors := errors + 1;
end if;
if launch_stb = '1' and sample_stb = '1' then
report what & ": both strobes asserted" severity error;
errors := errors + 1;
end if;
sclk_rise <= '0';
sclk_fall <= '0';
wait for 1 ns;
end procedure check;
begin
-- rests low => leading is rising, trailing is falling
idle_high <= '0'; sample_on_trailing <= '1'; wait for 1 ns;
check('1', '1', '0', "idle_low/sample_trailing rising");
check('0', '0', '1', "idle_low/sample_trailing falling");
idle_high <= '0'; sample_on_trailing <= '0'; wait for 1 ns;
check('1', '0', '1', "idle_low/sample_leading rising");
check('0', '1', '0', "idle_low/sample_leading falling");
-- rests high => leading is falling, trailing is rising
idle_high <= '1'; sample_on_trailing <= '1'; wait for 1 ns;
check('0', '1', '0', "idle_high/sample_trailing falling");
check('1', '0', '1', "idle_high/sample_trailing rising");
idle_high <= '1'; sample_on_trailing <= '0'; wait for 1 ns;
check('0', '0', '1', "idle_high/sample_leading falling");
check('1', '1', '0', "idle_high/sample_leading rising");
-- No physical edge must produce no strobe, in every configuration.
for cfg in 0 to 3 loop
if cfg / 2 = 1 then idle_high <= '1'; else idle_high <= '0'; end if;
if cfg mod 2 = 1 then sample_on_trailing <= '1';
else sample_on_trailing <= '0'; end if;
sclk_rise <= '0'; sclk_fall <= '0';
wait for 1 ns;
if launch_stb = '1' or sample_stb = '1' then
report "strobe asserted with no SCLK edge" severity error;
errors := errors + 1;
end if;
end loop;
if errors = 0 then
report "PASS: all 4 configurations map correctly, strobes exclusive"
severity note;
else
report "FAIL" severity error;
end if;
wait;
end process stim;
end architecture sim;What the three agree on, and where they differ
All three describe the same gates: two selections mapping physical edges to logical ones, then two complementary selections mapping logical edges to roles. No state, no clock, no reset, and the exclusivity holds by construction in every one of them.
The differences are almost entirely in the testbenches. SystemVerilog offers automatic tasks with typed string arguments; Verilog-2001 has static tasks and passes the label as a wide vector, which is the idiom you will meet in older code. VHDL uses a procedure declared inside the process, which gives it access to the process's errors variable directly — arguably the cleanest of the three for this shape of test. VHDL also forces the configuration sweep to be written with explicit conditionals rather than bit-slicing an integer, because std_logic and integers do not implicitly convert; that strictness is what stops an entire class of width bug elsewhere.
5. Why a Verification Engineer Cares
Two consequences, and the first changes how a monitor is written.
Sample where the receiver samples. A monitor that captures MOSI and MISO on the wrong edge produces wrong transactions even when the DUT is perfect — it reads the line while it is changing, or reads the previous bit. The correct sampling point is the configured sample edge, derived exactly as §4's hardware derives it, from the resting level and the edge-role configuration held in the agent's configuration object. Chapter 1.4's monitor fragment sampled on posedge sclk and was explicitly labelled a fragment for this reason; this chapter is where that placeholder gets its real answer.
The strongest timing check available at cycle level is exclusivity. The assertion in §4 — launch and sample never in the same cycle — is genuinely useful and genuinely limited, and the limitation is the lesson. A simulation cycle is not a nanosecond. Proving the two events landed in different simulation cycles says nothing about whether, on hardware, the data had settled at the far end before the receiver's setup window opened. That question belongs to static timing analysis against constraints and to measurement, and Chapter 2.7 draws the layers apart explicitly. An engineer who reports "the timing assertions pass" as evidence about a board is making a category error.
6. Failure Signature — Data Shifted by One Position
Symptom. Every received word equals the expected word shifted by one bit position, identically on every transfer, at every clock rate.
Candidate mechanisms. Three, and they are genuinely different faults. The edge-role configuration is inverted, so the receiver samples at the instant the transmitter launches and gets the previous bit. The resting level is wrong (Chapter 2.2), which displaces both roles by half a bit time and produces the same arithmetic. Or the first bit is being handled incorrectly — under one of the two edge-role configurations the first bit must be placed on the line before the first clock edge, and a design that instead waits for an edge starts the whole stream one position late.
The discriminating observations. Take them in order of cost. First, check reproducibility: a perfectly consistent offset at every frequency is a configuration fault, not a margin fault — margin failures are frequency-dependent and intermittent, which eliminates Chapter 2.4 and Chapter 2.7 immediately. Second, read the resting level while CS is deasserted and compare it against the datasheet; a mismatch is the answer. Third, if the resting level is right, capture the first bit time specifically and ask whether the transmitter presented data before the first edge or at it — that separates the first-bit case from a plain role inversion.
Why the last check is worth its effort. A role inversion corrupts every bit identically and is fixed by a configuration bit. A first-bit error corrupts the stream alignment and is an implementation bug in the transmitter, unfixable by configuration. They look identical in the received data and are entirely different work to resolve, so the capture of the first bit time is what stops an afternoon being spent toggling mode settings that were never wrong.
7. Common Misconceptions
8. Reason It Through
Work this before reading the answers.
A master and a slave are configured with the same resting level and the same edge-role setting, and the link works correctly at 1 MHz. At 20 MHz, the master's received data is intermittently wrong while the data the slave receives remains perfect. An engineer concludes the edge-role configuration must be wrong and starts changing it.
Why is that conclusion inconsistent with the evidence? Because a configuration fault is deterministic. If the roles were inverted, the corruption would be present at 1 MHz too, and it would be a consistent one-position offset rather than intermittent. Something that works at one frequency and fails at another is a margin problem, and margin problems belong to the delays that must fit inside the half period — not to which edge plays which role.
Why does only one direction fail? This is the strongest clue in the statement, and Chapter 1.6 supplied the reason. The two directions have structurally different budgets. MOSI travels one way with the clock arriving alongside it. MISO requires the clock to reach the slave, the slave to respond, and the data to travel back — a round trip whose delays all compete for the same half period. So MISO exhausts its budget first, and "the master receives badly while the slave receives fine" is the canonical signature of a return-path timing limit.
What should be measured? MISO at the master's pin, relative to the master's sample edge. If the line is still transitioning at or shortly before the sampling instant, the round trip has run out of room. That is a direct confirmation and takes one capture. The complementary check is to confirm the failure tracks frequency monotonically — working below some rate and failing above it, with no sudden reappearance — which is what a budget running out looks like.
What is the half-period number at 20 MHz? The period is 50 ns, so the budget is 25 ns for the slave's output delay plus the outbound and return propagation plus the master's setup requirement. At 1 MHz the same four delays had 500 ns and could not plausibly have failed. Writing the number down is often enough to end the argument.
What are the fixes, and which is a trap? In order of cost: use a delayed sampling point if the controller offers one, which widens the window by half a cycle at the expense of hold margin; shorten or de-load the SCLK and MISO routing; then, if necessary, lower the clock. The trap is changing the edge-role configuration until the symptom moves — which can appear to work, because a different role assignment changes where the sampling instant falls and may accidentally land it somewhere survivable. That leaves a link that is both misconfigured and marginal, and it will fail again on the next board.
9. Understanding Check
10. Summary
Each bit time carries two instructions, and they are opposed: a transmitter must be told when to change the line, and a receiver must be told when to look at it. Putting both on the same edge would have a receiver sampling a signal that is changing — undefined behaviour, not a wrong bit — so SPI puts them on the two different edges of the same clock.
The launch edge places a new bit on the line; the sample edge captures it. They are always different edges of the same bit time, and which logical edge takes which role is configuration — one that Module 3 names and combines with Chapter 2.2's resting level to produce the four standard modes. Both directions share the same two instants, because Chapter 1.3's ring shifts once per bit time at each end.
The interval between the two edges is half a period, and that is the budget every remaining chapter in this module spends: 12.5 ns at 40 MHz, 25 ns at 20 MHz. A bit occupies a full period on the wire, but the time available for a device's output delay, the board round trip and the receiver's setup requirement is the half — and analysing against the period instead is the standard way to conclude that a marginal link has twice the margin it really has.
In hardware the mapping is four multiplexers and no state: physical rise/fall strobes become logical leading/trailing according to the resting level, and those become launch/sample according to the role configuration. Deriving the two roles as complementary selections makes their separation structural — there is no configuration in which both fire — which is worth asserting precisely because a plausible-looking refactor could break it silently.
11. What Comes Next
The half-period budget now needs spending. Chapter 2.4 — Setup, Hold, and Timing Margin makes the receiver's stability requirement quantitative: what setup and hold actually are, why they define a window rather than an instant, what margin means, and what happens physically when it runs out. That is the chapter that turns "the data must be stable" from a principle into a number you can check a datasheet against.
Browse the path on the SPI curriculum index, or revisit Leading and Trailing Edges for the vocabulary this chapter assigns roles to, or The Shift-Register Mental Model for the ring whose single shift per bit time is why one pair of edges serves both directions.
Continue learning
Related tutorials
- Related topic
Bus Turnaround and the Contention Window
The overlap between one driver releasing and the next asserting: how long the gap really is once pad turn-off counts, why re-selecting the same device needs none, and the guard that enforces dead time only on a handover.
- Related topic
Extracting Setup, Hold, and Maximum SCLK
Which timing-table rows constrain you and which constrain the device, why a number without its load and corner is not a specification, how a delay on one line alone destroys margin, and the monitor that measures the real link against the datasheet.
- Related topic
Chip-Select Generation
Chip select is a state machine, not a wire: the three ways deriving it from a busy signal fails, why the between-frames pause and the between-transactions pause are opposites, and why a select for a slave that is not fitted must be refused.
- Related topic
MISO Generation and Launch Timing
Under CPHA=0 the master samples MISO on the very first SCLK edge, so the slave must drive before any clock has moved: deriving the lead a master must provide, why the slave cannot see a late first bit but can measure the interval exactly, why it cannot suppress its final launch, and a transmit path verified in three HDLs.
