The Four Modes: CPOL and CPHA
Draw any SPI mode with its launch and sample edges, fill the 4×4 mode table, and say why a wrong mode shifts by one bit instead of scrambling the byte.
- Lesson
- P7.2
- Time
- 35 min
- Runs on
- Your browser
- Check
- 4 questions
A display controller wrote pixels perfectly and read its status register as 52h instead of A5h, always exactly one bit off. The team searched for a bit-order bug for two days. The peripheral was mode 1 and the controller mode 0: each launched on the edge the other sampled.
- Cycle 2
The controller samples MISO on this rising edge. The peripheral launches on it too, 1 ns later.
- Cycle 18
Second byte: every bit the controller takes is the bit before. Bit 7 of the echo is late, and so is every bit after it.
Measured on the bench, two loopback bytes of A5h. The controller reads the second as 52h, A5h shifted one place right; the peripheral still gets A5h, with only 1 ns of hold.
Two Bits Define a Mode
CPOL is the level the clock rests at: 0 low, 1 high. CPHA picks the sampling edge: 0 samples the first edge away from rest, 1 the second. Each side launches on the opposite edge, so data has half a clock to settle.
- Rising-edge family
Modes 0 and 3 sample on rising edges. They differ only in where the clock rests, which matters at the edges of CS.
- Falling-edge family
Modes 1 and 2 sample on falling edges. A device from one family on a controller from the other launches on the edge the controller samples.
Read the diagram as text
- CPOL 0: clock rests low
- CPOL 1: clock rests high
- Mode 0: CPHA 0: first edge
- Mode 1: CPHA 1: second edge
- Mode 2: CPHA 0: first edge
- Mode 3: CPHA 1: second edge
- Samples rising: launches falling
- Samples falling: launches rising
- CPOL 0 → Mode 0
- CPOL 0 → Mode 1
- CPOL 1 → Mode 2
- CPOL 1 → Mode 3
- Mode 0 → Samples rising
- Mode 3 → Samples rising
- Mode 1 → Samples falling
- Mode 2 → Samples falling
Arrows lead from the rest level through the mode to its sampling edge.
Read phases and sample points (half clock)
- mode 0: columns 1 through 18.
- MOSI sampled at 3, 5, 7, 9, 11, 13, 15, 17.
- MISO sampled at 3, 5, 7, 9, 11, 13, 15, 17.
- Cycle 1
CPHA 0: the first bit is out as soon as CS falls, before any clock edge.
- Cycle 3
The first edge, rising, is the sample edge. Each side changes its output on the falling edge between samples.
Generated from the transaction and decoded back. Dots mark the sample edges.
Read phases and sample points (half clock)
- mode 1: columns 1 through 18.
- MOSI sampled at 3, 5, 7, 9, 11, 13, 15, 17.
- MISO sampled at 3, 5, 7, 9, 11, 13, 15, 17.
- Cycle 1
CPHA 1: nothing to sample yet. The first edge, rising, launches bit 7.
- Cycle 4
The second edge, falling, samples it. Launch and sample stay half a clock apart.
Generated from the transaction and decoded back. Dots mark the sample edges.
| Mode | CPOL, CPHA | Clock idles | Sample edge | Launch edge | First bit appears |
|---|---|---|---|---|---|
| 0 | 0, 0 | low | rising | falling | when CS falls |
| 1 | 0, 1 | low | falling | rising | on the first edge |
| 2 | 1, 0 | high | falling | rising | when CS falls |
| 3 | 1, 1 | high | rising | falling | on the first edge |
CPHA 0 samples the first (leading) edge away from idle, CPHA 1 the second (trailing) edge. Modes 0 and 3 share their edges, as do modes 1 and 2.
The flash samples its input on rising edges and changes its output on falling edges. That is modes 0 and 3, which it supports both, and which differ only in where the clock rests.
The 4×4 Matrix
Set the controller to one mode and the peripheral to another, send A5h both ways, and measure what each side got and how much hold each line had. Sixteen runs fill the table.
| Controller \ peripheral | mode 0 | mode 1 | mode 2 | mode 3 |
|---|---|---|---|---|
| mode 0 | a5 / a5, hold 6 ns | 52 / a5, hold 1 ns | 52 / a5, hold 1 ns | a5 / a5, hold 6 ns |
| mode 1 | d2 / d2, hold 1 ns | a5 / a5, hold 6 ns | a5 / a5, hold 6 ns | d2 / d2, hold 1 ns |
| mode 2 | 52 / a5, hold 1 ns | a5 / a5, hold 6 ns | a5 / a5, hold 6 ns | 52 / a5, hold 1 ns |
| mode 3 | a5 / a5, hold 6 ns | d2 / d2, hold 1 ns | d2 / d2, hold 1 ns | a5 / a5, hold 6 ns |
Measured: both sides send A5h and the peripheral echoes what it received. Each cell is the second transfer, "controller got / peripheral got", with the shorter hold time of the two lines. The flash needs 2 ns of hold (tCHDX, W25Q128JV §9.6).
Every pair that samples on the same edge works, with 6 ns of hold: modes 0 and 3 together, modes 1 and 2 together. Every other pair launches on the edge the other side samples. Only the 1 ns launch delay decides the race, and the bytes come back shifted by one bit.
Why a Wrong Mode Is a One-Bit Shift
Both sides still move one bit per clock. The only disagreement is which edge carries each bit, so the sampler takes every bit one launch early or late. A5h becomes 52h (one place right) or D2h, never noise.
| Controller mode 0, peripheral in | mode 0 | mode 1 | mode 2 | mode 3 |
|---|---|---|---|---|
| First byte after CS falls, A5h sent back | a5h | d2h | d2h | a5h |
Measured: a one-byte frame, the peripheral answering A5h from its transmit register (data/P7-2-measured.json).
The rest level still matters at the edges of CS. This model preloads bit 7 when CS falls without advancing its count, so the same-edge mode-0/mode-3 pair returns the first byte intact: A5h reads as a5h. Check the selected device’s CS and clock requirements before relying on a shared sampling edge.
Where You Will Meet It
Every SPI controller has a mode setting per device, and every bring-up has met a wrong one. A bench for it runs the device in all four controller modes and checks the bytes and the hold margin, not the bytes alone.
Asked in interviews: SPI modes
What do CPOL and CPHA mean?
CPOL is the idle level of the clock. CPHA selects whether data is sampled on the first (leading) or second (trailing) edge after the clock leaves idle; the other edge launches the next bit.
Which modes are compatible?
The model transfers intact bytes between 0 and 3 (rising), and between 1 and 2 (falling), including the first byte. The selected device must also accept the clock level at CS. Mixed pairs race on the shared edge.
What does a mode mismatch look like?
Bytes shifted by exactly one bit, often only in one direction, and hold times of a launch delay. It is never random garbage.
Which mode does a typical serial flash use?
Mode 0 or mode 3: it samples on rising edges and changes its output on falling edges.
Try It
The probe beside the bench records the last sample edge of each receiver and the shortest time from it to the next change of that receiver's input: the hold time each line actually had.
Set p0.tx_word = 8'ha5; and print received after the first transfer instead: the first byte after CS falls. The controller-0, peripheral-3 cell reads a5h: the first-bit preload also preserves this first transfer.
ctrl 0 periph 0: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 ns ctrl 0 periph 1: ctrl got 52, periph got a5, hold MOSI 1.0 MISO 1.0 ns ctrl 0 periph 2: ctrl got 52, periph got a5, hold MOSI 1.0 MISO 1.0 ns ctrl 0 periph 3: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 ns ctrl 1 periph 0: ctrl got d2, periph got d2, hold MOSI 1.0 MISO 1.0 ns ctrl 1 periph 1: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 ns ctrl 1 periph 2: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 ns ctrl 1 periph 3: ctrl got d2, periph got d2, hold MOSI 1.0 MISO 1.0 ns ctrl 2 periph 0: ctrl got 52, periph got a5, hold MOSI 1.0 MISO 1.0 ns ctrl 2 periph 1: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 ns ctrl 2 periph 2: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 ns ctrl 2 periph 3: ctrl got 52, periph got a5, hold MOSI 1.0 MISO 1.0 ns ctrl 3 periph 0: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 ns ctrl 3 periph 1: ctrl got d2, periph got d2, hold MOSI 1.0 MISO 1.0 ns ctrl 3 periph 2: ctrl got d2, periph got d2, hold MOSI 1.0 MISO 1.0 ns ctrl 3 periph 3: ctrl got a5, periph got a5, hold MOSI 6.0 MISO 6.0 nsRead included file: spi_periph.sv
`timescale 1ns/1ps
// A teaching register device, not the Winbond register map. Writes commit at CS
// rising only when the entire command/data frame ended on a byte boundary.
module spi_periph #(parameter CPOL=0,CPHA=0)(input wire cs_n,sclk,mosi,inout wire miso,output wire oe);
int cpol=CPOL,cpha=CPHA,wrong_mode=0;
bit drive_when_deselected=0,late_miso=0,lsb_first=0,loopback=0,chain=0;
realtime t_clqv=1.0;
reg [7:0] tx_word=8'h3c,last_rx=0,ctrl=0,data_reg=0,shift=0,response=8'h3c,pending=0;
reg out=0;int count=0,words=0,frames=0;reg [7:0] command=0;
wire p=cpol[0]^((wrong_mode&2)!=0),h=cpha[0]^((wrong_mode&1)!=0);
assign oe=!cs_n||drive_when_deselected;assign miso=oe?out:1'bz;
function automatic bit txbit(input int i);txbit=chain?shift[7]:response[lsb_first?i:7-i];endfunction
function automatic [7:0] reg_value(input reg [6:0] addr);
case(addr)0:reg_value=8'h3c;1:reg_value=ctrl;2:reg_value=data_reg;3:reg_value=frames[7:0];default:reg_value=0;endcase
endfunction
always @(negedge cs_n)begin
count=0;shift=chain?tx_word:0;words=0;command=0;response=tx_word;
// Preload bit 7 without advancing count. A same-edge 0/3 (or 2/1)
// pair may sample before the peripheral sees its first launch edge.
// CPHA=1's leading launch repeats this bit; only sampling advances count.
out<=#(t_clqv)txbit(0);
end
always @(posedge cs_n)begin
if(words>0)begin
if(!chain&&!loopback&&count==0&&words>=2&&command[7])begin
case(command[6:0])1:ctrl=pending;2:data_reg=pending;default:;endcase
end
frames++;if(chain)tx_word=shift;
end
end
always @(sclk)if(!cs_n)begin
if(sclk==(h?p:!p))begin
shift={shift[6:0],mosi};count++;
if(count==8)begin
last_rx=shift;count=0;
if(!chain)begin
if(loopback)response=shift;
else if(words==0)begin command=shift;response=shift[7]?0:reg_value(shift[6:0]);end
else pending=shift;
end
words++;
end
if(late_miso)out<=#(t_clqv)txbit(count);
end else if(!late_miso)out<=#(t_clqv)txbit(count);
end
endmodule
Read included file: spi_ctrl_tasks.svh
int cpol=0,cpha=0;realtime half_period=5.0,t_launch=1.0;bit cs_too_early=0,sample_late=0,cs_between_bytes=0;int clocks=0;
task automatic set_mode(input int p,input int h);cpol=p;cpha=h;sclk=p;endtask
task automatic set_period(input real ns);if(ns<=0)$fatal(1,"SPI period must be positive");half_period=ns/2;endtask
task automatic cs_lo(input int id=0);sclk=cpol;#(half_period);cs_n=0;#(half_period);endtask
task automatic cs_hi(input int id=0);#(half_period);cs_n=1;#(half_period);endtask
task automatic xfer(input reg [7:0] value,output reg [7:0] received);
received=0;
for(int b=7;b>=0;b--)begin
if(t_launch>=half_period)$fatal(1,"launch delay must fit half-period");
if(!cpha)begin
#(t_launch);mosi=value[b];#(half_period-t_launch);
if(!sample_late)received[b]=miso;sclk=!cpol;clocks++;
#(half_period);if(sample_late)received[b]=miso;sclk=cpol;
end else begin
#(half_period);sclk=!cpol;clocks++;#(t_launch);mosi=value[b];#(half_period-t_launch);
if(!sample_late)received[b]=miso;sclk=cpol;
if(sample_late)begin #(half_period);received[b]=miso;end
end
if(cs_too_early&&b==1)cs_n=1;
end
if(cs_between_bytes)begin cs_hi();cs_lo();end
endtask
Read included file: spi_checker.sv
`timescale 1ns/1ps
// Mode-generic teaching checker. tCLQV margin uses the configured receiver setup;
// positive margin is a bound, not a guarantee that a particular byte is correct.
module spi_checker(input wire cs_n,sclk,mosi,miso,oe,input wire cpol,cpha);
int broken=0,samples=0;realtime changed=0,mosi_changed=0,last_sample=-100,last_launch=-100,last_edge=-100,selected=-100,deselected=-100;
bit enabled=0,sample_late=0;realtime tdvch=1.0,tchdx=2.0,tclqv=1.0,receiver_setup=0.0;
realtime tslch=3.0,tchsh=3.0,tshch=3.0,tchsl=3.0,tshsl=10.0;
task automatic bad(input string rule,input real margin);broken++;$display("SPI CHECK %s margin %0.3f ns",rule,margin);endtask
// Late sampling consumes the old bit just before the next launch. Changes
// after that launch are legal; its setup bound is checked by tclqv below.
always @(miso)begin changed=$realtime;
if(enabled&&!sample_late&&!cs_n&&samples>0&&sclk==(cpha?cpol:!cpol))bad("same_edge",$realtime-last_sample);
end
always @(mosi)begin
mosi_changed=$realtime;
if(enabled&&!cs_n&&$realtime-last_sample<tchdx)bad("tchdx",$realtime-last_sample-tchdx);
end
task automatic timing_margin(input real half_period,input real tclqv_max,input real setup_ns);
real margin; margin=(sample_late?2:1)*half_period-tclqv_max-setup_ns;
$display("SPI MARGIN half=%0.3f tCLQV=%0.3f setup=%0.3f margin=%0.3f ns",half_period,tclqv_max,setup_ns,margin);
if(margin<0)bad("tclqv",margin);
endtask
always @(negedge cs_n)begin
if(sclk!==cpol)bad("cs_clk_idle",0);
if(enabled&&$realtime-deselected<tshsl)bad("tshsl",$realtime-deselected-tshsl);
if(enabled&&$realtime-last_edge<tchsl)bad("tchsl",$realtime-last_edge-tchsl);
selected=$realtime;samples=0;enabled=1;
end
always @(sclk)begin
if(enabled)begin
if(!cs_n&&$realtime-selected<tslch)bad("tslch",$realtime-selected-tslch);
if(cs_n&&$realtime-deselected<tshch)bad("tshch",$realtime-deselected-tshch);
end
last_edge=$realtime;
if(!cs_n)begin
if(sclk==(cpha?cpol:!cpol))begin
samples++;last_sample=$realtime;
if($realtime-mosi_changed<tdvch)bad("tdvch",$realtime-mosi_changed-tdvch);
if(!sample_late&&last_launch>=selected&&$realtime-last_launch<tclqv+receiver_setup)bad("tclqv",$realtime-last_launch-tclqv-receiver_setup);
end else begin
if(sample_late&&last_launch>=selected&&$realtime-last_launch<tclqv+receiver_setup)bad("tclqv",$realtime-last_launch-tclqv-receiver_setup);
last_launch=$realtime;
end
end
end
always @(posedge cs_n)if(enabled)begin
if(sclk!==cpol)bad("cs_clk_idle",0);
if($realtime-last_edge<tchsh)bad("tchsh",$realtime-last_edge-tchsh);
if(samples%8!=0)bad("byte_boundary",0);
deselected=$realtime;#1;if(oe)bad("miso_released",0);
end
endmodule
Read included file: spi_mon.sv
`timescale 1ns/1ps
module spi_mon(input wire cs_n,sclk,mosi,miso,input wire cpol,cpha);
reg [7:0] a=0,b=0;int count=0,frames=0;bit trace=0,sample_wrong_edge=0;
always @(negedge cs_n)count=0;
always @(sclk)if(!cs_n&&sclk==((cpha^sample_wrong_edge)?cpol:!cpol))begin
a={a[6:0],mosi};b={b[6:0],miso};count++;
if(count==8)begin frames++;if(trace)$display("SPI m%0d %02h/%02h",2*cpol+cpha,a,b);count=0;end
end
endmodule
Check Yourself
Remember
- CPOL is the clock's rest level; CPHA picks the sampling edge, first or second; the other edge launches.
- Modes 0 and 3 sample on rising edges, modes 1 and 2 on falling edges; pairs that share the edge transfer intact bytes in this model.
- Any other pair launches on the edge the other side samples: a race with a launch delay of hold, and a one-bit shift.
- The rest level matters at the CS edges, where the first bit appears.
- The flash samples on rising edges and changes its output on falling edges, so it works in modes 0 and 3.
Practice
- Check Mode and Edge Discipline Verify · Hard
- Diagnose the Displaced SPI Reply Debug · Medium