Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Digital Design & Verification (Verilog, SystemVerilog, VLSI)  Module 4 – Finite-state machines in RTL

FSM case study: UART and SPI controllers

Build a UART transmitter, a receiver that samples 16 times a bit and an SPI controller for all four modes as state machines, then verify them on models.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 3 interview questions.

What you will learn

  • Translate a serial protocol's timing description into state machines and counters
  • Implement a UART transmitter and a receiver that samples the line 16 times a bit
  • Implement an SPI controller with selectable clock polarity and phase
  • Verify serial links with loopback testbenches and with reference models

Before you start

What you’ll learn · Before you start

What you will learn

  • Translate a serial protocol's timing description into state machines and counters
  • Implement a UART transmitter and a receiver that samples the line 16 times a bit
  • Implement an SPI controller with selectable clock polarity and phase
  • Verify serial links with loopback testbenches and with reference models

Before you start

On this page

At 115,200 baud a bit lasts 8.68 microseconds, and a 12 MHz clock cannot count that out exactly: the nearest whole divider runs 7 % slow. Of 1,000 bytes from a UART built on it, a receiver at the right rate read 373 wrong and flagged 489 more as framing errors, while the UART’s own loopback test passed all 1,000. Serial controllers are state machines with counters, and they are where a plain timing description becomes RTL, and where a test that checks a design against itself can hide a mistake.

You will turn the UART’s frame into a transmitter with four states, generate its ticks with a divider and with a phase accumulator, build a receiver that samples the middle of each bit, verify both with models at the exact rate, and write an SPI controller for all four clock modes. Icarus Verilog 13.0 and Verilator 5.052 ran every simulation, and the rate calculations run in your browser.

A frame is a start bit, eight data bits and a stop bit, timed by a counter

A UART (universal asynchronous receiver-transmitter): A serial link with no shared clock: each byte travels as a frame of a start bit of 0, the data bits least significant first, an optional parity bit and a stop bit of 1, on a line that idles at 1. line idles at 1. Texas Instruments’ data sheet of its TL16C550D describes the frame such a device sends: a start bit of 0, five to eight data bits with bit 0, the least significant, sent first, an optional parity bit, and one, one and a half or two stop bits of 1. This lesson’s frame is 8N1: 8 data bits, no parity, one stop bit, ten bits in all. At a baud rate: The number of symbols a line carries per second; on a UART each symbol is one bit, so at 115,200 baud a bit lasts 1/115,200 s, about 8.68 microseconds. of 115,200, one bit lasts 1 ÷ 115,200 s, 8.68 microseconds.

The description turns into RTL in two parts. Every stretch of the frame that does something different becomes a state: IDLE, START, DATA and STOP. Every count inside a state becomes a counter: uart_tx.sv’s ticks_q counts the 16 ticks of a bit, and bit_q the data bit on the line. The machine decides what the line does and the counters decide when. The line itself comes from a flip-flop, tx_q, so it never glitches, the lesson before this one’s rule for an output that leaves the chip. tb_frame.sv sends 0x4B, the letter K, and prints every change of state or bit:

The transmitter sending one frame of 0x4B

hdl/uart_tx.sv

`timescale 1ns / 1ps
// A UART transmitter for 8N1 frames: the line idles at 1; a frame is a start bit of 0, the 8 data bits least
// significant first, and a stop bit of 1, each exactly 16 ticks of tick_i long. A byte is taken in a cycle where
// valid_i and ready_o are both 1. The line comes from a flip-flop, so it never glitches.
module uart_tx (
  input  logic       clk_i,
  input  logic       rst_ni,
  input  logic       tick_i,      // 16 per bit, from baud_gen
  input  logic       valid_i,
  input  logic [7:0] data_i,
  output logic       ready_o,
  output logic       tx_o
);
  typedef enum logic [1:0] {IDLE, START, DATA, STOP} state_t;

  state_t     state_q, state_d;
  logic [3:0] ticks_q, ticks_d;   // ticks into the current bit
  logic [2:0] bit_q, bit_d;       // the data bit on the line
  logic [7:0] shift_q, shift_d;   // the byte, shifted right as it goes out
  logic       tx_q, tx_d;
  logic       bit_end;            // the 16th tick of a bit

  assign bit_end = tick_i && (ticks_q == 4'd15);

  always_comb begin
    state_d = state_q;
    ticks_d = tick_i ? ticks_q + 4'd1 : ticks_q;
    bit_d   = bit_q;
    shift_d = shift_q;
    tx_d    = tx_q;
    unique case (state_q)
      IDLE: begin
        ticks_d = '0;
        if (valid_i) begin
          shift_d = data_i;
          state_d = START;
        end
      end
      START: begin
        if (tx_q) begin             // the start bit begins on a tick, so it lasts 16 of them
          ticks_d = '0;
          if (tick_i) tx_d = 1'b0;
        end else if (bit_end) begin
          state_d = DATA;
          bit_d   = '0;
          tx_d    = shift_q[0];
        end
      end
      DATA: if (bit_end) begin
        if (bit_q == 3'd7) begin
          state_d = STOP;
          tx_d    = 1'b1;
        end else begin
          bit_d   = bit_q + 3'd1;
          shift_d = shift_q >> 1;
          tx_d    = shift_q[1];
        end
      end
      STOP: if (bit_end) state_d = IDLE;
      default: begin
        state_d = IDLE;
        tx_d    = 1'b1;
      end
    endcase
  end

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      state_q <= IDLE;
      ticks_q <= '0;
      bit_q   <= '0;
      shift_q <= '0;
      tx_q    <= 1'b1;               // the idle line is high
    end else begin
      state_q <= state_d;
      ticks_q <= ticks_d;
      bit_q   <= bit_d;
      shift_q <= shift_d;
      tx_q    <= tx_d;
    end
  end

  assign ready_o = (state_q == IDLE);
  assign tx_o    = tx_q;
endmodule

hdl/baud_gen.sv

`timescale 1ns / 1ps
// A clock enable 16 times per bit for a UART: tick_o is 1 for one clk_i cycle at a time. With Divisor > 0 it counts
// clock cycles and ticks on every Divisor-th; with Divisor = 0 it adds Step to a 20-bit phase accumulator every cycle
// and ticks on each carry out, so on average the ticks come at Step / 2^20 of the clock rate.
module baud_gen #(
  parameter int unsigned Divisor = 0,        // 7 for 12 MHz and 115,200 baud: 12e6 / 7 / 16 = 107,143 baud
  parameter int unsigned Step    = 161061    // round(2^20 * 16 * 115200 / 12e6) = round(161061.27)
) (
  input  logic clk_i,
  input  logic rst_ni,
  output logic tick_o
);
  if (Divisor > 0) begin : g_divider
    localparam logic [7:0] Last = 8'(Divisor - 1);
    logic [7:0] count_q;
    always_ff @(posedge clk_i or negedge rst_ni) begin
      if (!rst_ni)              count_q <= '0;
      else if (count_q == Last) count_q <= '0;
      else                      count_q <= count_q + 8'd1;
    end
    assign tick_o = (count_q == Last);
  end else begin : g_accumulator
    logic [20:0] acc_q;                      // one more bit than the accumulator: the carry
    always_ff @(posedge clk_i or negedge rst_ni) begin
      if (!rst_ni) acc_q <= '0;
      else         acc_q <= {1'b0, acc_q[19:0]} + 21'(Step);
    end
    assign tick_o = acc_q[20];
  end
endmodule

hdl/tb_frame.sv

`timescale 1ns / 1ps
// One frame of 0x4B, the letter K, from the transmitter on a 12 MHz clock with the phase-accumulator ticks. Just
// after every clock edge the testbench looks at the line and at the transmitter's state and bit index, and prints a
// line when the start bit falls and whenever the state or the bit changes: the time since the start bit fell, how
// long the bit before lasted, and the line. One bit at 115,200 baud lasts 8.68 microseconds.
module tb_frame;
  logic clk = 1'b0, rst_n = 1'b0, tick, tx, ready, valid = 1'b0;
  always #(1000.0 / 24.0) clk = ~clk;          // 12 MHz

  baud_gen u_baud (.clk_i(clk), .rst_ni(rst_n), .tick_o(tick));
  uart_tx  u_tx   (.clk_i(clk), .rst_ni(rst_n), .tick_i(tick), .valid_i(valid), .data_i(8'h4B), .ready_o(ready),
                   .tx_o(tx));

  realtime fell = -1, last = 0;
  string   was = "IDLE", now;
  int      bit_was = 0;

  task automatic show(input string what);
    $display("t = %6.2f us (+%4.2f)  %s, line %b", ($realtime - fell) / 1000.0, ($realtime - last) / 1000.0, what,
             tx);
    last = $realtime;
  endtask

  always @(posedge clk) begin
    #1;
    now = u_tx.state_q.name();
    if (rst_n && fell < 0 && tx === 1'b0) begin
      fell = $realtime;
      last = $realtime;
      show("START, the start bit");
    end else if (now != was || (now == "DATA" && u_tx.bit_q != bit_was)) begin
      if (now == "DATA") show($sformatf("DATA, bit %0d", u_tx.bit_q));
      else if (now == "STOP") show("STOP, the stop bit");
      else if (now == "IDLE" && fell >= 0) show("IDLE again");
    end
    was = now;
    bit_was = u_tx.bit_q;
  end

  initial begin
    #1000 rst_n = 1'b1;
    @(posedge clk) valid <= 1'b1;
    @(posedge clk) valid <= 1'b0;
    wait (tx === 1'b0);
    wait (ready);
    #100 $finish;
  end
endmodule

Commands · hdl/frame_session.py

from session import run

# One frame of 0x4B from the transmitter, on Icarus Verilog and on Verilator.
run("""
iverilog -g2012 -Wall -o frame.vvp tb_frame.sv baud_gen.sv uart_tx.sv
vvp -n frame.vvp
""")

Output

# Icarus Verilog 13.0 (stable)
$ iverilog -g2012 -Wall -o frame.vvp tb_frame.sv baud_gen.sv uart_tx.sv
uart_tx.sv:46: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_tx.sv:56: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_tx.sv:31: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
$ vvp -n frame.vvp
t =   0.00 us (+0.00)  START, the start bit, line 0
t =   8.67 us (+8.67)  DATA, bit 0, line 1
t =  17.33 us (+8.67)  DATA, bit 1, line 1
t =  26.08 us (+8.75)  DATA, bit 2, line 0
t =  34.75 us (+8.67)  DATA, bit 3, line 1
t =  43.42 us (+8.67)  DATA, bit 4, line 0
t =  52.08 us (+8.67)  DATA, bit 5, line 0
t =  60.75 us (+8.67)  DATA, bit 6, line 1
t =  69.42 us (+8.67)  DATA, bit 7, line 0
t =  78.17 us (+8.75)  STOP, the stop bit, line 1
t =  86.83 us (+8.67)  IDLE again, line 1
tb_frame.sv:46: $finish called at 88559041 (1ps)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 frame_session.py

The sorry lines are Icarus Verilog’s notes on its own limits, not errors: a block that reads one bit of shift_q wakes on a change of any of its bits, and the checks of the unique qualifier are skipped, which changes none of the values the blocks compute. The data bits went out as 1, 1, 0, 1, 0, 0, 1, 0, which is 4b read from the least significant bit up, and the frame ended at 86.83 microseconds against 86.81 for ten bits at the exact rate. Step through it:

Interactive · stage by stage The transmitter sending the letter K, bit by bit Step through one frame of 0x4B; the times are the ones the simulation printed.
The transmitter sending the letter K, bit by bit, step 8 of 8. Showing IDLE, START (line 0, 16 ticks), DATA (bit 7 = 0), STOP (line 1, 16 ticks). In focus: DATA.frame overIDLEline 1STARTline 0, 16 ticksDATAbit 7 = 0STOPline 1, 16 ticks

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. A byte is taken. The line idles at 1. The byte 4b is taken in a cycle where valid_i and ready_o are both 1, and the state becomes START, with the byte in the shift register.
  2. The start bit. At the next tick the transmitter drives the line to 0, the start bit, at t = 0. From now on ticks_q counts the 16 ticks of each bit.
  3. Bit 0 first. On the 16th tick the state becomes DATA and the line takes bit 0 of 4b, which is 1, at 8.67 microseconds. The least significant bit goes first.
  4. The next bits. Every 16 ticks the shift register moves one place right and the line takes the next bit. Bit 1 = 1 at 17.33, bit 2 = 0 at 26.08, bit 3 = 1 at 34.75 microseconds.
  5. Bits 4 to 7. Bits 4 to 7 follow, 0, 0, 1 and 0, read in that order. bit_q reaches 7 at 69.42 microseconds.
  6. The stop bit. After bit 7 the state becomes STOP and the line returns to 1 for a whole bit, at 78.17 microseconds. A receiver checks that it is there.
  7. Ready again. Sixteen ticks later, at 86.83 microseconds, the frame is over and the state is IDLE, so ready_o is 1. Ten bits at 8.68 microseconds would be 86.81.
  8. Why 8.67 or 8.75. Sixteen ticks are 104 or 105 cycles of the 12 MHz clock, because the accumulator's ticks come 6 or 7 cycles apart. Each bit is a little short or long, and the frame does not drift.

START waits for a tick before it drops the line, so the start bit lasts 16 ticks like every other bit; dropping it at once would make it up to a tick short. The bits lasted 8.67 or 8.75 microseconds, 104 or 105 clock cycles, never 8.68, which is the next section’s subject.

Checkpoint

Check: A UART frame as states and counters

  1. Question 1 of 2 The transmitter sends 0x4B, binary 01001011, in an 8N1 frame. What does the line carry, in time order?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0, then 1 1 0 1 0 0 1 0, then 1

    An 8N1 frame is ten bits: start 0, the eight data bits from bit 0 up, stop 1. For 4b the data bits are 1, 1, 0, 1, 0, 0, 1, 0.

  2. Question 2 of 2 A frame needs eight data bits of 16 ticks each. Why does uart_tx.sv use one DATA state with counters, not eight states with one more counter?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The eight bits do the same thing for the same time; only the bit index and the tick count differ, which counters hold

    A protocol becomes a state per stretch that does something different, IDLE, START, DATA and STOP, and a counter for every count inside a stretch: ticks_q for the 16 ticks, bit_q for the data bit.

Sixteen ticks a bit need a divider that fits, or an accumulator

The same data sheet gives the rule for its baud generator: its output is 16 times the baud rate, and the divisor is the input clock divided by 16 times the baud rate. For 12 MHz and 115,200 baud that is 12,000,000 ÷ 1,843,200, about 6.51. A counter can divide only by a whole number, and the nearest, 7, ticks at 1.714 MHz, which makes 107,143 baud, 6.99 % slow.

A phase accumulator: A register that adds a fixed step every clock cycle and gives a tick on each carry out of its top bit, so ticks come on average at step / 2^n of the clock rate, a fraction no whole-number divider can reach. divides by a fraction instead. baud_gen.sv adds a step of 161,061 to a 20-bit register every cycle and ticks on each carry out of it, so on average it ticks 161,061 ÷ 2²⁰ of the clock rate, 1,843,197 times a second, 0.0002 % slow. Its ticks come 6 or 7 cycles apart, which is why the bits above lasted 104 or 105 cycles, but they never drift. baud_error.py works out both methods for a few clocks and rates, then what a 7 % error does to a frame. Add a pair of your own to PAIRS and run it:

Divider and accumulator errors, and a slow receiver Python · model/baud_error.py
"""How far a UART's bit rate lands from the one asked for, with 16 ticks per bit, for a few clocks and rates: an
integer divisor, round(clock / (16 * baud)), against a 20-bit phase accumulator that adds
round(2**20 * 16 * baud / clock) every cycle. Then where a receiver that runs about 7 % slow samples each bit of a
frame sent at the right rate.
"""

ACC_BITS = 20
PAIRS = [(12_000_000, 9_600), (12_000_000, 115_200), (25_000_000, 115_200), (50_000_000, 115_200),
         (100_000_000, 921_600)]


def rate_error(clock, baud):
    """(divisor, its error in %, accumulator step, its error in %)."""
    divisor = round(clock / (16 * baud))
    by_divisor = clock / (16 * divisor)
    step = round(2**ACC_BITS * 16 * baud / clock)
    by_accumulator = clock * step / 2**ACC_BITS / 16
    return divisor, 100 * (by_divisor / baud - 1), step, 100 * (by_accumulator / baud - 1)


print("   clock      baud | divisor  error   | accumulator step  error")
for clock, baud in PAIRS:
    divisor, d_err, step, a_err = rate_error(clock, baud)
    print(f"{clock / 1e6:5.0f} MHz {baud:9,} | {divisor:7} {d_err:+7.3f} % | {step:16,} {a_err:+8.4f} %")

# The stop bit's middle is 9.5 bits after the start edge, and a receiver at 16 ticks a bit may see that edge up to
# one tick, 1/16 of a bit, late. Drifting half a bit by then means sampling the wrong bit.
print(f"\nmismatch a frame survives: about {100 * (0.5 - 1 / 16) / 9.5:.1f} % (half a bit, less a tick, over 9.5 bits)")

# A receiver at 12 MHz / 7 / 16 = 107,143 baud reading a frame sent at 115,200: where its samples land, in the
# sender's bits (the start bit is 0 to 1, data bit k is k + 1 to k + 2, the stop bit is 9 to 10).
slow = 115_200 / (12_000_000 / 7 / 16)
names = ["start"] + [f"bit {k}" for k in range(8)] + ["stop"]
print(f"\na receiver 7 % slow samples each bit {slow:.4f} of the sender's bits apart:")
for i, name in enumerate(names):
    at = (i + 0.5) * slow
    landed = names[int(at)] if int(at) < len(names) else "after the frame"
    note = "" if landed == name else f"  <- reads {landed}"
    print(f"  {name:>5}: sampled at {at:5.2f}{note}")

Output

   clock      baud | divisor  error   | accumulator step  error
   12 MHz     9,600 |      78  +0.160 % |           13,422  +0.0017 %
   12 MHz   115,200 |       7  -6.994 % |          161,061  -0.0002 %
   25 MHz   115,200 |      14  -3.119 % |           77,309  -0.0005 %
   50 MHz   115,200 |      27  +0.469 % |           38,655  +0.0008 %
  100 MHz   921,600 |       7  -3.119 % |          154,619  +0.0001 %

mismatch a frame survives: about 4.6 % (half a bit, less a tick, over 9.5 bits)

a receiver 7 % slow samples each bit 1.0752 of the sender's bits apart:
  start: sampled at  0.54
  bit 0: sampled at  1.61
  bit 1: sampled at  2.69
  bit 2: sampled at  3.76
  bit 3: sampled at  4.84
  bit 4: sampled at  5.91
  bit 5: sampled at  6.99
  bit 6: sampled at  8.06  <- reads bit 7
  bit 7: sampled at  9.14  <- reads stop
   stop: sampled at 10.21  <- reads after the frame

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 baud_error.py

How much error a frame survives

The middle of the stop bit is 9.5 bits after the start edge, and a receiver that samples 16 times a bit may see that edge up to a sixteenth of a bit late. Drifting half a bit by then means sampling the wrong bit, so the two ends together can be about (0.5 − 1/16) ÷ 9.5 = 4.6 % apart.

Every rate in the table was within 3.2 % with a divider except 12 MHz at 115,200, at 7 %. The last lines show what 7 % does to a receiver: each of its samples lands 1.0752 of the sender’s bits after the one before, so bit 6 is read from bit 7, bit 7 from the stop bit, and the stop bit after the frame has ended. An accumulator of 20 bits put every pair within 0.002 %.

Checkpoint

Check: Dividers and phase accumulators

  1. Question 1 of 2 A divider of 7 makes 16 ticks a bit from a 12 MHz clock. What baud rate does it give, to the nearest whole number?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 107143 baud (anything from 107142 to 107144 counts)

    12,000,000 ÷ 7 = 1,714,286 ticks a second, and ÷ 16 = 107,143 baud, 6.99 % below 115,200.

  2. Question 2 of 2 Why can the two ends of an 8N1 link be only about 4.6 % apart?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The middle of the stop bit is 9.5 bits after the start edge, and drifting half a bit, less the sixteenth the receiver may see the edge late, means sampling the wrong bit

    Each frame starts the count again at its start edge, so only the drift within one frame matters: half a bit by the middle of the stop bit, less the receiver's uncertainty about the edge.

The receiver samples each bit in its middle

uart_rx.sv reads the line 16 times a bit, which is called oversampling: Sampling a signal several times per bit, such as 16 times for a UART receiver, so that the receiver can find an edge and then the middle of each bit without the sender's clock.. The line comes from another device, so it can change at any moment, and two flip-flops in a row first bring it into this clock’s time, a two-flip-flop synchronizer: Two flip-flops in a row in the receiving clock domain. The first may go metastable when its input changes near an edge; the second samples it a clock period later, once it has almost surely settled. More in “Metastability, MTBF and two-flop synchronizers”, which the clock-domain module of this track explains in full. Then the machine runs on the ticks:

Interactive · stage by stage The receiver reading the letter K at 16 ticks a bit Step through one frame on the line; each step says which tick the receiver looks at, and why there.
The receiver reading the letter K at 16 ticks a bit, step 7 of 7. Showing IDLE, START (still 0: a start bit), DATA (shift_q = 4b), STOP (1: valid, 4b). In focus: IDLE.a glitchbyte or errorIDLEwaits for a 0STARTstill 0: a start bitDATAshift_q = 4bSTOP1: valid, 4bbyte or error

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Into this clock. The line changes whenever the far end likes. Two flip-flops bring it into this clock's time first, so the receiver sees every change two cycles late, and never half-way.
  2. The line falls. At a tick the receiver sees 0 for the first time. It may be a start bit or a short glitch, so the state becomes START and ticks_q starts at 0.
  3. Half a bit later. Eight ticks on, in the middle of the start bit, the line is still 0, so it is a real start bit. A pulse shorter than half a bit would be 1 again here and go back to IDLE.
  4. Bit 0. Sixteen ticks after the middle of the start bit comes the middle of bit 0. The receiver samples 1 and shifts it in from the left.
  5. Seven more bits. Every 16 ticks after that it samples the middle of the next bit, 1, 0, 1, 0, 0, 1 and 0. After bit 7 the shift register holds 4b.
  6. The stop bit. Sixteen ticks after bit 7, in the middle of the stop bit, the line is 1, so valid_o is 1 for one cycle with data_o = 4b. A 0 here would set frame_err_o instead.
  7. Ready early. The receiver is back in IDLE in the middle of the stop bit, half a bit before the frame ends, so a start bit that follows at once is never missed.

Each choice has its reason. Looking 8 ticks after the line falls puts the check in the middle of the start bit, where a pulse shorter than half a bit has already gone, so a glitch on the line sends the receiver back to IDLE instead of into a byte; the TL16C550D lists the same false-start detection among its features. Sampling every 16 ticks from there lands in the middle of each later bit, as far as possible from both of its edges, which is what lets the two ends differ by a few per cent. A stop bit of 0 is a framing error: A UART frame whose stop bit was received as 0 instead of 1, usually because the two ends run at rates too far apart or the receiver lost the frame's start., the data sheet’s “did not have a valid (set) stop bit”, and the receiver reports it on frame_err_o instead of valid_o.

Sampling where the line changes

Wrong: count 16 ticks from the falling edge for bit 0, which samples at the very edge between the start bit and bit 0, where a small rate error picks either. Right: check the start bit 8 ticks after the fall, then count 16 ticks from there, so every sample is half a bit from both edges.

Checkpoint

Check: Sampling in the middle of each bit

  1. Question 1 of 2 Why does the receiver look at the line again 8 ticks after it first sees a 0?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: That is the middle of the start bit; if the line is 1 again there, the 0 was a glitch and the receiver goes back to IDLE

    Checking at the start bit's middle rejects short glitches, and gives the reference from which every later sample is 16 ticks, a whole bit, apart.

  2. Question 2 of 2 The receiver returns to IDLE in the middle of the stop bit, not at its end. What would go wrong if it waited for the end?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: With a receiver a little slower than the sender, the next start bit could already have begun, and the receiver would miss its falling edge

    Leaving STOP half a bit early gives the receiver slack for frames that follow immediately, the case the test sends.

A loopback test shares the mistake; a model does not

tb_uart.sv checks the link three ways at once, with 1,000 bytes from a 32-bit xorshift generator that gives the same bytes on every simulator. A model receiver samples the transmitter’s line in real time at exactly 115,200 baud, a model transmitter drives a receiver at that rate, and a second receiver reads the transmitter’s own line, a loopback test: A test that connects a design's transmitter to its own receiver. It shows that the two halves agree with each other, but not that either meets the specification, since a mistake both share cancels out.. The models know nothing of the design’s ticks:

Interactive · step by step Three ways to check one link, and which of them see a wrong rate Step through the testbench's three paths; the last steps show what each one reported with the divider of 7.
Three ways to check one link, and which of them see a wrong rate, step 6 of 6. Showing uart_tx, model receiver (373 wrong, 489 framing), uart_rx, loopback (1000 of 1000, no error), model transmitter, uart_rx (316 wrong, 575 framing). In focus: uart_rx, loopback.uart_txdesign ticksmodel receiver373 wrong, 489 framinguart_rx, loopback1000 of 1000, no errormodel transmitter115,200 baud, real timeuart_rx316 wrong, 575 framing

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The design sends. The transmitter sends 1,000 seeded bytes as fast as it can, timed by the design's ticks.
  2. A model reads it. A model receiver samples that line in real time at exactly 115,200 baud. It knows nothing of the ticks, so it checks the transmitter's rate.
  3. The loopback. A copy of the receiver reads the same line through the same ticks. It checks that the two halves of the design agree with each other.
  4. A model sends. A model transmitter drives a third line at exactly 115,200 baud into another receiver, which checks the receiver's rate.
  5. The divider of 7. With ticks from the divider of 7, 7 % slow, the model receiver found 373 wrong bytes and 489 framing errors, and the receiver fed by the model 316 and 575.
  6. The loopback passes. The loopback decoded all 1,000 bytes without an error. Both of its ends run 7 % slow together, so it cannot see a wrong rate.

The session builds the testbench twice on Icarus Verilog, once with each tick generator, and then once on Verilator with the accumulator, after a lint of the design files:

1,000 bytes three ways, with each tick generator

hdl/uart_rx.sv

`timescale 1ns / 1ps
// A UART receiver for 8N1 frames that looks at the line 16 times per bit. Two flip-flops first bring the line,
// which changes whenever the far end likes, into this clock's time (the clock-domain module explains why). The receiver
// waits for the line to fall, checks the start bit again 8 ticks later, in its middle, then samples each data bit and
// the stop bit in the middle, every 16 ticks. A stop bit of 0 is a framing error.
module uart_rx (
  input  logic       clk_i,
  input  logic       rst_ni,
  input  logic       tick_i,      // 16 per bit, from baud_gen
  input  logic       rx_i,        // the line, from outside this clock's time
  output logic       valid_o,     // one cycle: data_o holds a byte
  output logic [7:0] data_o,
  output logic       frame_err_o  // one cycle: the stop bit was 0
);
  typedef enum logic [1:0] {IDLE, START, DATA, STOP} state_t;

  state_t     state_q, state_d;
  logic [1:0] sync_q;             // the synchroniser: rx_i two clock edges late
  logic [3:0] ticks_q, ticks_d;
  logic [2:0] bit_q, bit_d;
  logic [7:0] shift_q, shift_d;   // bits come in least significant first, from the left
  logic       valid_d, err_d;
  logic       rx;

  assign rx = sync_q[1];

  always_comb begin
    state_d = state_q;
    ticks_d = tick_i ? ticks_q + 4'd1 : ticks_q;
    bit_d   = bit_q;
    shift_d = shift_q;
    valid_d = 1'b0;
    err_d   = 1'b0;
    unique case (state_q)
      IDLE: begin
        ticks_d = '0;
        if (tick_i && !rx) state_d = START;          // the line fell: maybe a start bit
      end
      START: if (tick_i && ticks_q == 4'd7) begin    // 8 ticks on: the middle of the start bit
        ticks_d = '0;
        if (!rx) begin
          state_d = DATA;
          bit_d   = '0;
        end else begin
          state_d = IDLE;                            // only a glitch: back to waiting
        end
      end
      DATA: if (tick_i && ticks_q == 4'd15) begin    // 16 ticks on: the middle of the next bit
        shift_d = {rx, shift_q[7:1]};
        if (bit_q == 3'd7) state_d = STOP;
        else               bit_d = bit_q + 3'd1;
      end
      STOP: if (tick_i && ticks_q == 4'd15) begin
        state_d = IDLE;
        if (rx) valid_d = 1'b1;
        else    err_d   = 1'b1;
      end
      default: state_d = IDLE;
    endcase
  end

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      sync_q      <= 2'b11;            // the idle line is high
      state_q     <= IDLE;
      ticks_q     <= '0;
      bit_q       <= '0;
      shift_q     <= '0;
      valid_o     <= 1'b0;
      frame_err_o <= 1'b0;
    end else begin
      sync_q      <= {sync_q[0], rx_i};
      state_q     <= state_d;
      ticks_q     <= ticks_d;
      bit_q       <= bit_d;
      shift_q     <= shift_d;
      valid_o     <= valid_d;
      frame_err_o <= err_d;
    end
  end

  assign data_o = shift_q;
endmodule

hdl/tb_uart.sv

// 1,000 seeded bytes three ways at once, on a 12 MHz clock: the transmitter's line read by a model receiver that
// samples at exactly 115,200 baud; a model transmitter at exactly 115,200 baud read by the receiver; and the
// transmitter's line looped back into a second receiver. DIVISOR picks the tick generator: 0 is the phase
// accumulator, 7 the integer divisor. The models know nothing of the design's ticks: they use real time.
`timescale 1ns / 1ps
module tb_uart;
  localparam int  Bytes = 1000;
  localparam real Bit   = 1.0e9 / 115200.0;       // 8680.556 ns
  parameter int   DIVISOR = 0;

  logic clk = 1'b0, rst_n = 1'b0;
  always #(1000.0 / 24.0) clk = ~clk;             // 12 MHz

  logic       tick, tx, tx_valid = 1'b0, tx_ready, model_line = 1'b1;
  logic [7:0] tx_data = '0;
  logic       loop_valid, loop_err, rx_valid, rx_err;
  logic [7:0] loop_data, rx_data;

  baud_gen #(.Divisor(DIVISOR)) u_baud (.clk_i(clk), .rst_ni(rst_n), .tick_o(tick));
  uart_tx u_tx   (.clk_i(clk), .rst_ni(rst_n), .tick_i(tick), .valid_i(tx_valid), .data_i(tx_data),
                  .ready_o(tx_ready), .tx_o(tx));
  uart_rx u_loop (.clk_i(clk), .rst_ni(rst_n), .tick_i(tick), .rx_i(tx),
                  .valid_o(loop_valid), .data_o(loop_data), .frame_err_o(loop_err));
  uart_rx u_rx   (.clk_i(clk), .rst_ni(rst_n), .tick_i(tick), .rx_i(model_line),
                  .valid_o(rx_valid), .data_o(rx_data), .frame_err_o(rx_err));

  // The same bytes on every simulator: a 32-bit xorshift from seed 1, its low 8 bits.
  function automatic logic [7:0] byte_at(input int n);
    logic [31:0] x = 32'd1;
    for (int i = 0; i <= n; i++) begin
      x ^= x << 13;
      x ^= x >> 17;
      x ^= x << 5;
    end
    return x[7:0];
  endfunction

  logic [7:0] sent [Bytes];
  initial for (int i = 0; i < Bytes; i++) sent[i] = byte_at(i);

  int model_n = 0, model_bad = 0, model_ferr = 0;   // the transmitter, read by the model
  int rx_n = 0, rx_bad = 0, rx_ferr = 0;            // the model transmitter, read by the receiver
  int loop_n = 0, loop_bad = 0, loop_ferr = 0;      // the loopback

  // The design's transmitter sends every byte as soon as it is ready.
  initial begin
    #1000 rst_n = 1'b1;
    for (int i = 0; i < Bytes; i++) begin
      @(posedge clk);
      while (!tx_ready) @(posedge clk);
      tx_valid <= 1'b1;
      tx_data  <= sent[i];
      @(posedge clk);
      tx_valid <= 1'b0;
      @(posedge clk);                             // ready falls after the byte is taken
    end
  end

  // The model receiver: on a falling edge of the line, it waits one and a half bits to the middle of bit 0.
  initial begin
    logic [7:0] b;
    @(posedge rst_n);
    for (int k = 0; k < Bytes; k++) begin
      @(negedge tx);
      #(1.5 * Bit);
      for (int i = 0; i < 8; i++) begin
        b[i] = tx;
        if (i < 7) #(Bit);
      end
      #(Bit);
      if (!tx) model_ferr++;
      else if (b != sent[k]) model_bad++;
      model_n++;
    end
  end

  // The model transmitter: start bit, 8 data bits least significant first, stop bit, at exactly 115,200 baud.
  initial begin
    #5000;
    for (int k = 0; k < Bytes; k++) begin
      model_line = 1'b0;
      #(Bit);
      for (int i = 0; i < 8; i++) begin
        model_line = sent[k][i];
        #(Bit);
      end
      model_line = 1'b1;
      #(Bit);
    end
  end

  always @(posedge clk) begin
    if (rx_valid) begin
      if (rx_n >= Bytes || rx_data != sent[rx_n]) rx_bad++;
      rx_n++;
    end
    if (rx_err) begin
      rx_ferr++;
      rx_n++;
    end
    if (loop_valid) begin
      if (loop_n >= Bytes || loop_data != sent[loop_n]) loop_bad++;
      loop_n++;
    end
    if (loop_err) begin
      loop_ferr++;
      loop_n++;
    end
  end

  initial begin
    #(1.2 * Bytes * 10 * Bit);                    // long enough for every frame, 20 % to spare
    if (DIVISOR == 0) $display("tick generator: phase accumulator");
    else              $display("tick generator: divide by %0d", DIVISOR);
    $display("  transmitter -> model at 115,200: %4d frames, %4d wrong bytes, %4d framing errors", model_n, model_bad, model_ferr);
    $display("  model at 115,200 -> receiver:    %4d frames, %4d wrong bytes, %4d framing errors", rx_n, rx_bad, rx_ferr);
    $display("  transmitter -> receiver (loop):  %4d frames, %4d wrong bytes, %4d framing errors", loop_n, loop_bad, loop_ferr);
    $finish;
  end
endmodule

Commands · hdl/uart_session.py

from session import run

# 1,000 seeded bytes three ways, on Icarus Verilog: first with the phase-accumulator ticks, then with the integer
# divisor of 7 (-P sets the testbench's parameter).
run("""
iverilog -g2012 -Wall -o acc.vvp tb_uart.sv baud_gen.sv uart_tx.sv uart_rx.sv
vvp -n acc.vvp
iverilog -g2012 -Wall -Ptb_uart.DIVISOR=7 -o div.vvp tb_uart.sv baud_gen.sv uart_tx.sv uart_rx.sv
vvp -n div.vvp
""")

Output

# Icarus Verilog 13.0 (stable)
$ iverilog -g2012 -Wall -o acc.vvp tb_uart.sv baud_gen.sv uart_tx.sv uart_rx.sv
uart_tx.sv:46: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_tx.sv:56: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_rx.sv:49: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_rx.sv:49: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_tx.sv:31: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
uart_rx.sv:34: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
uart_rx.sv:34: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
$ vvp -n acc.vvp
tick generator: phase accumulator
  transmitter -> model at 115,200: 1000 frames,    0 wrong bytes,    0 framing errors
  model at 115,200 -> receiver:    1000 frames,    0 wrong bytes,    0 framing errors
  transmitter -> receiver (loop):  1000 frames,    0 wrong bytes,    0 framing errors
tb_uart.sv:118: $finish called at 104166666667 (1ps)
$ iverilog -g2012 -Wall -Ptb_uart.DIVISOR=7 -o div.vvp tb_uart.sv baud_gen.sv uart_tx.sv uart_rx.sv
uart_tx.sv:46: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_tx.sv:56: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_rx.sv:49: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_rx.sv:49: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'shift_q[7:0]').
uart_tx.sv:31: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
uart_rx.sv:34: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
uart_rx.sv:34: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
$ vvp -n div.vvp
tick generator: divide by 7
  transmitter -> model at 115,200: 1000 frames,  373 wrong bytes,  489 framing errors
  model at 115,200 -> receiver:     892 frames,  316 wrong bytes,  575 framing errors
  transmitter -> receiver (loop):  1000 frames,    0 wrong bytes,    0 framing errors
tb_uart.sv:118: $finish called at 104166666667 (1ps)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 uart_session.py

The accumulator build on Verilator, after a lint Python · hdl/uart_verilator_session.py
from session import run

# A lint of each design file on its own, then the same 1,000 bytes with the phase-accumulator ticks, on Verilator.
run("""
verilator --lint-only -Wall --quiet baud_gen.sv
verilator --lint-only -Wall --quiet uart_tx.sv
verilator --lint-only -Wall --quiet uart_rx.sv
verilator --lint-only -Wall --quiet spi_ctrl.sv
verilator --binary -j 0 --quiet --top-module tb_uart tb_uart.sv baud_gen.sv uart_tx.sv uart_rx.sv
obj_dir/Vtb_uart +verilator+quiet
""")

Output

# Verilator 5.052
$ verilator --lint-only -Wall --quiet baud_gen.sv
$ verilator --lint-only -Wall --quiet uart_tx.sv
$ verilator --lint-only -Wall --quiet uart_rx.sv
$ verilator --lint-only -Wall --quiet spi_ctrl.sv
$ verilator --binary -j 0 --quiet --top-module tb_uart tb_uart.sv baud_gen.sv uart_tx.sv uart_rx.sv
$ obj_dir/Vtb_uart +verilator+quiet
tick generator: phase accumulator
  transmitter -> model at 115,200: 1000 frames,    0 wrong bytes,    0 framing errors
  model at 115,200 -> receiver:    1000 frames,    0 wrong bytes,    0 framing errors
  transmitter -> receiver (loop):  1000 frames,    0 wrong bytes,    0 framing errors
- tb_uart.sv:118: Verilog $finish

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 uart_verilator_session.py

Path Phase accumulator Divider of 7
transmitter to the model receiver 1,000 bytes, no error 373 wrong bytes, 489 framing errors
the model transmitter to the receiver 1,000 bytes, no error 892 frames: 316 wrong, 575 framing errors
transmitter to receiver, loopback 1,000 bytes, no error 1,000 bytes, no error

With the accumulator every path decoded every byte on both simulators. With the divider of 7, both paths through a model failed, and the loopback passed all 1,000, because both of its ends were 7 % slow together and agreed with each other perfectly. The receiver fed by the model even lost 108 frames, 892 of 1,000: after a framing error it took the middle of a frame for a new start bit.

Key idea

A loopback test shows that the two halves of a design agree with each other; only a model that runs at the specified rate shows that they agree with the rest of the world.

Checkpoint

Check: Loopback tests and models

  1. Question 1 of 2 With the divider of 7, why did the loopback decode all 1,000 bytes when both paths through a model failed?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Its transmitter and receiver share the same ticks, so both are 7 % slow and agree with each other

    A loopback shows that the two halves agree. Only a reference at the specified rate, here the models in real time, shows that the rate itself is right.

  2. Question 2 of 2 What makes the model receiver of tb_uart.sv independent of the design?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It times its samples in real time, 8,680.556 ns a bit, and knows nothing of the design's ticks

    A model checks a design only where it does not share the design's assumptions; here the rate.

SPI: the clock’s polarity and phase decide which edge samples

SPI (serial peripheral interface): A synchronous serial bus with no formal standard: a controller drives a clock and a chip select, and on every clock period one bit goes each way, on MOSI to the peripheral and on MISO back. has no formal specification, as the Linux kernel’s documentation of it says. A controller drives the clock, sclk, and a chip select: The signal that picks one device on a shared bus, usually active low: an SPI peripheral listens and drives MISO only while its chip select is asserted., usually active low, and the two sides exchange one bit each way on every clock period, MOSI towards the peripheral and MISO back, most often most significant bit first. Two mode bits fix the timing, in the kernel’s words: CPOL is “the initial clock polarity”, and CPHA picks the edge that samples, the leading one for 0 and the trailing one for 1. The SPI mode: One of four timings of an SPI bus, numbered with CPOL as the high bit and CPHA as the low bit: CPOL is the level the clock rests at, CPHA chooses whether data is sampled on the leading or the trailing edge of each bit. number has CPOL as its high bit:

Mode CPOL, CPHA Clock rests at Data sampled on Data changes on
0 0, 0 0 rising, the leading edge falling
1 0, 1 0 falling, the trailing edge rising
2 1, 0 1 falling, the leading edge rising
3 1, 1 1 rising, the trailing edge falling

spi_ctrl.sv is a machine of three states, IDLE, SHIFT and HOLD, with a counter of half periods and one of the 16 edges of a transfer; the mode bits decide which edges sample and which change MOSI, and with CPHA 0 bit 7 is on the line before the first edge. tb_spi.sv runs it against a model peripheral written from the table alone, which answers transfer n with the byte 3c + n and whose outputs, like the controller’s, change 2 ns after the edge that moves them:

256 transfers in each mode, two of them drawn, then a mode mismatch

hdl/spi_ctrl.sv

`timescale 1ns / 1ps
// An SPI controller, the side that drives the clock, for 8-bit transfers, most significant bit first, in any of the
// four modes. sclk_o rests at cpol_i between transfers. With cpha_i = 0 both sides sample on the first edge of each
// bit and change their data on the second; with cpha_i = 1 they change on the first edge and sample on the second.
// cs_no is low for the whole transfer, and every output comes from a flip-flop.
module spi_ctrl #(
  parameter int unsigned Half = 3          // clk_i cycles per half period of sclk_o
) (
  input  logic       clk_i,
  input  logic       rst_ni,
  input  logic       cpol_i,
  input  logic       cpha_i,
  input  logic       start_i,              // taken when busy_o is 0
  input  logic [7:0] tx_i,
  output logic       busy_o,
  output logic       done_o,               // one cycle: rx_o holds the byte read
  output logic [7:0] rx_o,
  output logic       cs_no,
  output logic       sclk_o,
  output logic       mosi_o,
  input  logic       miso_i
);
  typedef enum logic [1:0] {IDLE, SHIFT, HOLD} state_t;

  state_t     state_q, state_d;
  logic [3:0] wait_q, wait_d;              // cycles left in this half period
  logic [4:0] edges_q, edges_d;            // sclk edges so far: 16 in a transfer
  logic [7:0] tx_q, tx_d, rx_q, rx_d;
  logic       cs_q, cs_d, sclk_q, sclk_d, mosi_q, mosi_d, done_d;
  logic       step;                        // this cycle makes the next edge of sclk
  logic       odd;                         // that edge is the first edge of a bit (edges 1, 3 ... 15)

  assign odd = !edges_q[0];

  always_comb begin
    state_d = state_q;
    wait_d  = (wait_q != '0) ? wait_q - 4'd1 : wait_q;
    edges_d = edges_q;
    tx_d    = tx_q;
    rx_d    = rx_q;
    cs_d    = cs_q;
    sclk_d  = sclk_q;
    mosi_d  = mosi_q;
    done_d  = 1'b0;
    step    = 1'b0;
    unique case (state_q)
      IDLE: begin
        sclk_d = cpol_i;                   // the clock rests at its polarity
        if (start_i) begin
          state_d = SHIFT;
          cs_d    = 1'b0;
          tx_d    = tx_i;
          edges_d = '0;
          wait_d  = 4'(Half - 1);          // half a period from cs_no falling to edge 1
          if (!cpha_i) mosi_d = tx_i[7];   // CPHA 0: the first bit is on the line before edge 1
        end
      end
      SHIFT: if (wait_q == '0) begin
        if (edges_q == 5'd16) begin
          state_d = HOLD;
          wait_d  = 4'(Half - 1);
        end else begin
          step = 1'b1;
        end
      end
      HOLD: if (wait_q == '0) begin
        state_d = IDLE;
        cs_d    = 1'b1;
        done_d  = 1'b1;
      end
      default: begin
        state_d = IDLE;
        cs_d    = 1'b1;
      end
    endcase
    if (step) begin
      wait_d  = 4'(Half - 1);
      sclk_d  = !sclk_q;
      edges_d = edges_q + 5'd1;
      if (odd != cpha_i) rx_d = {rx_q[6:0], miso_i};  // sample: odd edges for CPHA 0, even for CPHA 1
      if (!cpha_i && !odd) begin                       // CPHA 0 changes on even edges
        mosi_d = tx_q[6];
        tx_d   = {tx_q[6:0], 1'b0};
      end
      if (cpha_i && odd) mosi_d = tx_q[7];             // CPHA 1 changes on odd edges
      if (cpha_i && !odd) tx_d = {tx_q[6:0], 1'b0};    // and readies the next bit after sampling
    end
  end

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      state_q <= IDLE;
      wait_q  <= '0;
      edges_q <= '0;
      tx_q    <= '0;
      rx_q    <= '0;
      cs_q    <= 1'b1;
      sclk_q  <= 1'b0;
      mosi_q  <= 1'b0;
      done_o  <= 1'b0;
    end else begin
      state_q <= state_d;
      wait_q  <= wait_d;
      edges_q <= edges_d;
      tx_q    <= tx_d;
      rx_q    <= rx_d;
      cs_q    <= cs_d;
      sclk_q  <= sclk_d;
      mosi_q  <= mosi_d;
      done_o  <= done_d;
    end
  end

  assign busy_o = (state_q != IDLE);
  assign rx_o   = rx_q;
  assign cs_no  = cs_q;
  assign sclk_o = sclk_q;
  assign mosi_o = mosi_q;
endmodule

hdl/tb_spi.sv

// The SPI controller against a model of a peripheral, written from the mode rules alone. The peripheral answers
// transfer n with the byte 3c + n and keeps every byte it receives. Each side's data reaches the other 2 ns after the
// clock edge that changes it, as a real output does. 256 transfers in each of the four modes with the peripheral in the
// same mode, the first transfer of modes 0 and 3 drawn as text (one column per half period of sclk), and then a
// controller in mode 0 against a peripheral in mode 1.
`timescale 1ns / 1ps
module tb_spi;
  logic clk = 1'b0, rst_n = 1'b0;
  always #10 clk = ~clk;                       // 50 MHz; sclk is 50 / (2 * 3) = 8.33 MHz

  logic       cpol, cpha, start = 1'b0, busy, done, cs_n, sclk, mosi_q, mosi, miso = 1'b0;
  logic [7:0] tx = '0, rx;
  spi_ctrl #(.Half(3)) u_ctrl (.clk_i(clk), .rst_ni(rst_n), .cpol_i(cpol), .cpha_i(cpha), .start_i(start),
                               .tx_i(tx), .busy_o(busy), .done_o(done), .rx_o(rx), .cs_no(cs_n), .sclk_o(sclk),
                               .mosi_o(mosi_q), .miso_i(miso));
  assign #2 mosi = mosi_q;                     // the controller's data, 2 ns after its clock edge

  // ---- the peripheral model ----
  logic       p_cpol, p_cpha;                  // the peripheral's own mode
  logic [7:0] p_in, p_out, p_got [256];
  int         p_n = 0;                         // transfers so far
  always @(negedge cs_n) begin
    p_out = 8'h3c + 8'(p_n);
    if (!p_cpha) miso <= #2 p_out[7];          // CPHA 0: the first bit as soon as it is selected
  end
  always @(sclk) if (!cs_n) begin
    bit lead;
    lead = (sclk != p_cpol);                   // a leading edge moves sclk away from where it rests
    if (lead != p_cpha) begin                  // its sampling edge: leading for CPHA 0, trailing for CPHA 1
      p_in = {p_in[6:0], mosi};
      if (p_cpha) p_out = {p_out[6:0], 1'b0};  // CPHA 1: the next bit goes out on the next leading edge
    end else if (p_cpha) begin
      miso <= #2 p_out[7];                     // CPHA 1: change on the leading edge
    end else begin
      p_out = {p_out[6:0], 1'b0};              // CPHA 0: change on the trailing edge
      miso <= #2 p_out[7];
    end
  end
  always @(posedge cs_n) if (rst_n) begin
    p_got[p_n % 256] = p_in;
    p_n++;
  end

  // ---- one transfer, and its waveform as text, one column per half period of sclk ----
  string w_sclk, w_mosi, w_miso;
  bit    draw = 1'b0;
  always @(posedge clk) if (draw && !cs_n && u_ctrl.wait_q == 4'd1) begin
    w_sclk = {w_sclk, sclk ? "1" : "0"};
    w_mosi = {w_mosi, mosi ? "1" : "0"};
    w_miso = {w_miso, miso ? "1" : "0"};
  end

  task automatic transfer(input logic [7:0] b, output logic [7:0] got);
    @(posedge clk);
    while (busy) @(posedge clk);
    tx    <= b;
    start <= 1'b1;
    @(posedge clk);
    start <= 1'b0;
    @(posedge done);
    got = rx;
  endtask

  task automatic run_mode(input int c_mode, input int p_mode, input bit show);
    logic [7:0] got, sent [256];
    int read_wrong = 0, got_wrong = 0;
    {cpol, cpha} = 2'(c_mode);
    {p_cpol, p_cpha} = 2'(p_mode);
    p_n = 0;
    repeat (4) @(posedge clk);
    for (int i = 0; i < 256; i++) begin
      sent[i] = 8'(i * 37 + 75);               // 4b first, then every byte once
      draw = show && i == 0;
      if (draw) begin
        w_sclk = ""; w_mosi = ""; w_miso = "";
      end
      transfer(sent[i], got);
      if (draw) begin
        $display("mode %0d: sent %h, read %h", c_mode, sent[i], got);
        $display("  sclk %s", w_sclk);
        $display("  mosi %s", w_mosi);
        $display("  miso %s", w_miso);
      end
      if (got !== 8'h3c + 8'(i)) begin
        if (read_wrong == 0) $display("  first byte read wrong: %h instead of %h", got, 8'h3c + 8'(i));
        read_wrong++;
      end
    end
    for (int i = 0; i < 256; i++) if (p_got[i] !== sent[i]) begin
      if (got_wrong == 0) $display("  first byte received wrong: %h instead of %h", p_got[i], sent[i]);
      got_wrong++;
    end
    $display("controller mode %0d, peripheral mode %0d: 256 transfers, %0d read wrong, %0d received wrong",
             c_mode, p_mode, read_wrong, got_wrong);
  endtask

  initial begin
    {cpol, cpha} = 2'b00;
    #100 rst_n = 1'b1;
    run_mode(0, 0, 1);
    run_mode(1, 1, 0);
    run_mode(2, 2, 0);
    run_mode(3, 3, 1);
    run_mode(0, 1, 0);
    $finish;
  end
endmodule

Commands · hdl/spi_session.py

from session import run

# 256 transfers in each of the four modes against the peripheral model, two of them drawn, then a mode mismatch.
run("""
iverilog -g2012 -Wall -o spi.vvp tb_spi.sv spi_ctrl.sv
vvp -n spi.vvp
""")

Output

# Icarus Verilog 13.0 (stable)
$ iverilog -g2012 -Wall -o spi.vvp tb_spi.sv spi_ctrl.sv
spi_ctrl.sv:55: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'tx_i[7:0]').
spi_ctrl.sv:80: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'rx_q[7:0]').
spi_ctrl.sv:82: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'tx_q[7:0]').
spi_ctrl.sv:83: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'tx_q[7:0]').
spi_ctrl.sv:85: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'tx_q[7:0]').
spi_ctrl.sv:86: sorry: constant selects in always_* processes are not fully supported (the process will be sensitive to all bits in 'tx_q[7:0]').
spi_ctrl.sv:46: vvp.tgt sorry: Case unique/unique0 qualities are ignored.
$ vvp -n spi.vvp
mode 0: sent 4b, read 3c
  sclk 010101010101010100
  mosi 001100001100111100
  miso 000011111111000000
controller mode 0, peripheral mode 0: 256 transfers, 0 read wrong, 0 received wrong
controller mode 1, peripheral mode 1: 256 transfers, 0 read wrong, 0 received wrong
controller mode 2, peripheral mode 2: 256 transfers, 0 read wrong, 0 received wrong
mode 3: sent 4b, read 3c
  sclk 101010101010101011
  mosi 000110000110011111
  miso 000001111111100000
controller mode 3, peripheral mode 3: 256 transfers, 0 read wrong, 0 received wrong
  first byte read wrong: 9e instead of 3c
controller mode 0, peripheral mode 1: 256 transfers, 256 read wrong, 0 received wrong
tb_spi.sv:105: $finish called at 1434090000 (1ps)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 spi_session.py

In every mode the controller read 3c + n in all 256 transfers, and the peripheral received every byte. The drawings have one column per half period of sclk, from the half period after cs_no falls to the one after the last edge. In mode 0 the clock rests at 0 and MOSI already holds 0, bit 7 of 4b, before the first rising edge; in mode 3 the clock rests at 1 and each bit appears one column later, on a falling edge, the leading one, and is sampled on the next rising edge. With the controller in mode 0 and the peripheral in mode 1, all 256 reads were wrong, the first 9e instead of 3c. The peripheral changed MISO on the rising edges, the very edges at which the controller sampled, so the controller read every bit one edge late, and the line’s stale 1 as bit 7. Step through a transfer in mode 0:

Interactive · message by message One SPI transfer in mode 0, edge by edge Step through the transfer that sent 4b and read 3c, then see what a peripheral in mode 1 does to it.
One SPI transfer in mode 0, edge by edge, step 7 of 7. Messages so far: spi_ctrl to Peripheral: cs_no = 0; spi_ctrl to Peripheral: sclk rises; Peripheral to spi_ctrl: MISO bit 6; spi_ctrl to Peripheral: 7 more bits; spi_ctrl to Peripheral: cs_no = 1; Peripheral to spi_ctrl: MISO too late; Peripheral to spi_ctrl: 9e read.spi_ctrlsends 4bPeripheralanswers 3ccs_no = 0sclk risesMISO bit 67 more bitscs_no = 1MISO too late9e read

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Selected. cs_no falls. In mode 0 the clock rests at 0, and both sides put bit 7 on their data line at once, MOSI 0 for 4b and MISO 0 for 3c.
  2. Edge 1 rises. The leading edge, a rising one in mode 0, is the sampling edge. The peripheral reads MOSI, 0, and the controller reads MISO, 0.
  3. Edge 2 falls. The trailing edge changes the data. Both sides put out bit 6, MOSI 1 and MISO 0, and have half a period to settle before the next rising edge.
  4. Edges 3 to 16. The same pattern seven more times, sampling on every rising edge and changing on every falling one, most significant bit first. Sixteen edges move eight bits each way.
  5. Deselected. Half a period after edge 16 the controller raises cs_no. It read 3c and the peripheral received 4b; every one of the 256 transfers in each mode came out right.
  6. A mode 1 peripheral. A peripheral in mode 1 changes MISO on the rising edges, the very edges at which the mode 0 controller samples, and its new bit comes 2 ns after the edge.
  7. One edge late. So the controller reads each bit one edge late, with the line's stale 1 in front of them. 3c, 00111100, arrives as 9e, 10011110, and all 256 reads were wrong.

A mismatch that works in one direction

The peripheral in mode 1 received every byte correctly, only because the controller’s MOSI changed 2 ns after the edge at which the peripheral sampled it. In silicon that margin is a hold time, and it may be gone on another board. Match the mode, and read the peripheral’s data sheet for it.

Checkpoint

Check: SPI clock polarity and phase

  1. Question 1 of 2 An SPI peripheral's data sheet shows the clock resting high and data sampled on the trailing edge. Which mode is it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Mode 3, CPOL 1 and CPHA 1

    The mode number is CPOL times 2 plus CPHA: 1 × 2 + 1 = 3.

  2. Question 2 of 2 A mode 0 controller read 9e where a mode 1 peripheral sent 3c. Why that value?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The peripheral changed MISO on the edges the controller sampled, so each bit arrived one edge late, with the line's stale 1 in front

    Sampling on the edge at which the other side changes reads the bit before. That gives a byte shifted by one, the typical sign of a mode mismatch.

Practise: parity and breaks in the receiver’s model

The exercise models the receiver in Python, line samples in and frames out, and adds two checks the RTL lacks: a parity bit, even or odd, and a line held low for a whole frame, a break (UART): A UART line held at 0 for longer than a whole frame, which a receiver reports as a break rather than as a byte with a framing error., which the TL16C550D reports when the input stays low for longer than a full word. Its sample tests include frames sent 2 % fast and 2 % slow.

Exercise · Medium · Python

The UART receiver as a model, with parity and breaks

Model the receiver of the case study in Python, and add the two checks it lacks: parity and line breaks.

receive(samples, parity) takes samples, the line as the receiver sees it 16 times a bit (a list of 0 and 1, where 1 is the idle level), and parity, one of "none", "even" and "odd". It returns the frames it found, in order, as a list of events:

  • ("byte", value) for a good frame;
  • ("parity", value) when the parity bit is wrong: with even parity the data bits and the parity bit hold an even number of 1s, with odd parity an odd number;
  • ("frame", value) when the stop bit is 0, a framing error;
  • ("break",) when every bit of the frame, data, parity and stop, was 0: the line was held low.

Sample the way the hardware does. A frame starts at the first 0 after a 1, at sample s. Sample s + 8, the middle of the start bit, must still be 0, or the 0 was a glitch and the search goes on from s + 1. Data bit k (least significant first) is sample s + 8 + 16 * (k + 1), then come the parity bit, if any, and the stop bit, 16 samples apart. After a frame, wait for the line to be 1 again before looking for the next start. A frame cut off by the end of the list is not reported.

Starter code · uart_rx.py

def receive(samples, parity="none"):
    """The frames in a list of line samples taken 16 times a bit, as a list of events."""
    events = []
    return events
The sample tests · test_uart_rx.py
from uart_rx import receive


def frame(byte, parity="none", stop=1, per_bit=16.0):
    """Samples of one frame, least significant bit first, with per_bit samples a bit (not always a whole number)."""
    bits = [0] + [(byte >> k) & 1 for k in range(8)]
    if parity != "none":
        ones = sum(bits[1:])
        bits.append(ones % 2 if parity == "even" else 1 - ones % 2)
    bits.append(stop)
    count = round(per_bit * len(bits))
    return [bits[min(int(n / per_bit), len(bits) - 1)] for n in range(count)]


IDLE = [1] * 20


def test_one_byte():
    """decodes one 8N1 frame of 0x4B"""
    assert receive(IDLE + frame(0x4B) + IDLE) == [("byte", 0x4B)]


def test_back_to_back():
    """decodes frames that follow each other with no idle time between them"""
    line = IDLE + frame(0x00) + frame(0xFF) + frame(0x81) + IDLE
    assert receive(line) == [("byte", 0x00), ("byte", 0xFF), ("byte", 0x81)]


def test_parity():
    """checks even and odd parity, and reports a byte whose parity bit is wrong"""
    assert receive(IDLE + frame(0x4B, "even") + IDLE, "even") == [("byte", 0x4B)]
    assert receive(IDLE + frame(0x4B, "odd") + IDLE, "odd") == [("byte", 0x4B)]
    assert receive(IDLE + frame(0x4B, "even") + IDLE, "odd") == [("parity", 0x4B)]
    assert receive(IDLE + frame(0x07, "odd") + IDLE, "even") == [("parity", 0x07)]


def test_framing_error():
    """reports a stop bit of 0 as a framing error, then finds the next frame"""
    line = IDLE + frame(0x55, stop=0) + IDLE + frame(0x2A) + IDLE
    assert receive(line) == [("frame", 0x55), ("byte", 0x2A)]


def test_break():
    """reports a line held at 0 for a whole frame as a break, once, and waits for the line to rise"""
    line = IDLE + [0] * 400 + IDLE + frame(0x31) + IDLE
    assert receive(line) == [("break",), ("byte", 0x31)]
    assert receive(IDLE + [0] * 400 + IDLE, "even") == [("break",)]


def test_glitch():
    """ignores a 0 that has gone again by the middle of the start bit"""
    line = IDLE + [0, 0, 0] + IDLE + frame(0x5A) + IDLE
    assert receive(line) == [("byte", 0x5A)]


def test_start_bit_check():
    """checks the start bit at sample s + 8: a 0 still there starts a frame, a 0 gone by then was a glitch"""
    assert receive(IDLE + [0] * 8 + [1] * 200) == []
    assert receive(IDLE + [0] * 9 + [1] * 200) == [("byte", 0xFF)]


def test_cut_off():
    """does not report a frame that the end of the samples cuts off, wherever the cut falls"""
    assert receive(IDLE + frame(0x4B) + IDLE + [0] * 4) == [("byte", 0x4B)]
    assert receive(IDLE + frame(0x4B) + IDLE + frame(0x2A)[:100]) == [("byte", 0x4B)]


def test_rate_off_by_two_percent():
    """still decodes back-to-back frames sent 2 % fast or 2 % slow"""
    for per_bit in (15.68, 16.32):
        line = IDLE + frame(0xC3, per_bit=per_bit) + frame(0x3C, per_bit=per_bit) + IDLE
        assert receive(line) == [("byte", 0xC3), ("byte", 0x3C)]
        assert receive(IDLE + frame(0x3C, "even", per_bit=per_bit) + IDLE, "even") == [("byte", 0x3C)]
A hint

Find the start, check s + 8, then read every bit of the frame into a list at its sample, s + 8 + 16 * (k + 1) for k = 0, 1, 2 and so on. Decide in this order: all bits 0 is a break, a stop bit of 0 is a framing error, then parity.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

The self-check covers frames, tick generators, the receiver, the three checks of a link and SPI modes, starting with the idea you have answered least well:

Practice that adapts to you

Practise this lesson

10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 What level does a UART line rest at between frames?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: 1

    Idle at 1, start bit 0, data, stop bit 1: the stop bit is the idle level again.

  2. Question 2 of 10 How long does one 8N1 frame take at 115,200 baud, in microseconds?

    Type a number.

    Show the answer: Question 2 of 10

    Answer: 86.8 microseconds (anything from 86.7 to 86.9 counts)

    Ten bits of 8.68 microseconds each: 86.8 microseconds. The simulation ended the frame at 86.83.

  3. Question 3 of 10 A 20-bit phase accumulator on a 12 MHz clock must tick 16 times a bit at 9,600 baud. What step does it add each cycle, to the nearest whole number?

    Type a number.

    A hint

    The ticks come at step ÷ 2²⁰ of the clock rate.

    Show the answer: Question 3 of 10

    Answer: 13422 per cycle (anything from 13421 to 13423 counts)

    2²⁰ × 16 × 9,600 ÷ 12,000,000 = 13,421.8, so 13,422, which baud_error.py also printed.

  4. Question 4 of 10 The accumulator's ticks come 6 or 7 cycles apart. Why does that not add up to an error over a frame?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: The carry comes on average at exactly step ÷ 2²⁰ of the clock, so each short gap is made up by a long one and the timing never drifts

    An integer divider has a fixed error that adds up bit after bit; an accumulator has a jitter of one cycle that never adds up.

  5. Question 5 of 10 What is the first thing uart_rx.sv does with rx_i?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: It passes it through two flip-flops, a synchroniser, into its own clock's time

    Logic on a clock must not read a signal that can change between its edges; two flip-flops first give a value caught mid-change time to settle.

  6. Question 6 of 10 The receiver first sees the start bit's 0 at tick 0. At which tick does it sample data bit 3?

    Type a number.

    A hint

    The start bit is checked at tick 8, and each later bit is 16 ticks after the one before.

    Show the answer: Question 6 of 10

    Answer: 72 ticks

    Tick 8 is the start bit's middle, bit 0 is at 24, bit 1 at 40, bit 2 at 56 and bit 3 at 72.

  7. Question 7 of 10 What does a loopback test check by itself?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: That the transmitter and the receiver of the same design agree with each other

    Loopbacks are useful and cheap, and they share every mistake common to both ends.

  8. Question 8 of 10 A team keeps only the loopback test, "because it passed with every tick generator we tried". What would you add, and why?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: A model at the specified rate on each direction, because only a reference outside the design can catch a mistake both halves share

    Every test checks against something. The loopback checks the design against itself; the models check it against the specification.

  9. Question 9 of 10 In mode 0, when must the controller put bit 7 on MOSI?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: Before the first edge, when cs_no falls, because the first edge already samples it

    CPHA 0 means "sample on the leading edge", so every bit must be on the line before its first edge; the first one as soon as the peripheral is selected.

  10. Question 10 of 10 What is the mode number of an SPI device with CPOL 1 and CPHA 0?

    Type a number.

    Show the answer: Question 10 of 10

    Answer: 2 (mode)

    CPOL is the high bit and CPHA the low bit: 1 × 2 + 0 = 2.

Interview questions

Warm-up (fresher): why does a UART receiver sample the line 16 times a bit instead of once?

Because the receiver does not know the sender’s clock. All it has is the falling edge of the start bit, so it needs a finer clock to find that edge and then the middle of every bit after it. With 16 ticks a bit, it sees the edge within a sixteenth of a bit, checks the start bit 8 ticks later, in its middle, to reject a short glitch, and samples each later bit 16 ticks after the one before, half a bit from both of its edges. That margin is what lets the two ends run at slightly different rates: about 4.6 % between them for an 8N1 frame. Sampling once a bit would put the sample wherever the two clocks happened to line up, often right at an edge.

The harder questions are about a divider that passes its loopback test, an SPI sensor whose bytes come back shifted, and a fast link between two clocks. Decide your first step, then compare it with the one given for each:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A UART on a 12 MHz FPGA clock must run at 115,200 baud. A colleague divides the clock by 7 for the 16 ticks a bit and shows a loopback test that passed 10,000 bytes. Is the design right? How would you test it, and how would you fix it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Bytes read back from an SPI sensor look shifted right by one bit, with a stray bit on top, while the sensor accepts the commands written to it. What do you suspect, how do you confirm it, and how do you fix it?

    Model answer, Pro. First step and follow-ups

  3. Senior

    Two boards talk over a UART at 921,600 baud. One makes its 16 ticks a bit from 100 MHz with a whole-number divider; the other runs from a crystal 1 % fast. Will frames get through, with how much margin, and what would you change?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What does an 8N1 UART frame contain?

    A start bit of 0, eight data bits with the least significant first, no parity and one stop bit of 1: ten bits in all, on a line that idles at 1.

  2. How long does one bit last at 115,200 baud?

    1 / 115,200 of a second, about 8.68 microseconds, so an 8N1 frame takes about 86.8 microseconds.

  3. Why can't a counter on a 12 MHz clock make 16 ticks a bit at 115,200 baud exactly?

    The divisor is 12,000,000 / (16 x 115,200), about 6.51, and a counter divides only by a whole number. The nearest, 7, gives 107,143 baud, 6.99 % slow.

  4. How does a phase accumulator make ticks at a fractional rate?

    It adds a fixed step to a register every cycle and ticks on each carry out. A step of 161,061 in a 20-bit register gives 1,843,197 ticks a second from 12 MHz, within 0.0002 %; ticks come 6 or 7 clock cycles apart, and their average holds.

  5. Why does the UART receiver check the start bit 8 ticks after the line falls?

    That puts the check in the middle of the start bit, so a pulse shorter than half a bit sends it back to IDLE; sampling every 16 ticks from there lands half a bit from both edges of every later bit.

  6. How far apart may the two ends of an 8N1 link run?

    About 4.6 % in all. The stop bit's middle lies 9.5 bits past the start edge, and with 16 samples a bit the receiver may find that edge up to 1/16 of a bit late: (0.5 - 1/16) / 9.5.

  7. Why did the UART's loopback test pass with a tick generator 7 % slow?

    Both ends of the loopback were slow together, so they agreed with each other. Only the model sender and receiver at the exact rate showed hundreds of wrong bytes and framing errors.

  8. What do CPOL and CPHA set in SPI?

    CPOL is the level at which the clock rests, and CPHA picks the edge that samples: the leading edge for 0, the trailing edge for 1. The mode number has CPOL as its high bit, so mode 3 rests at 1 and samples on rising edges.

Key takeaways

  • A UART frame becomes states for each stretch of the line, IDLE, START, DATA and STOP, and counters for the ticks of a bit and the bits of a byte; 0x4B left the transmitter as 1, 1, 0, 1, 0, 0, 1, 0, least significant bit first.
  • Sixteen ticks a bit at 115,200 baud from 12 MHz need a divisor of 6.51; the whole number 7 runs 6.99 % slow, while a 20-bit phase accumulator came within 0.0002 %.
  • The receiver checks the start bit 8 ticks after the fall and samples every 16 ticks from there, half a bit from the edges, which lets two ends differ by about 4.6 % in all.
  • With the divider of 7 the loopback decoded all 1,000 bytes while both paths through a model failed: a loopback cannot see a mistake that both halves share.
  • SPI’s CPOL sets the clock’s resting level and CPHA the edge that samples: the controller passed 256 transfers in each of the four modes, and a peripheral in mode 1 made every read of the mode 0 controller wrong, 9e for 3c.

Terms in this lesson

baud rate
The number of symbols a line carries per second; on a UART each symbol is one bit, so at 115,200 baud a bit lasts 1/115,200 s, about 8.68 microseconds.
break (UART)
A UART line held at 0 for longer than a whole frame, which a receiver reports as a break rather than as a byte with a framing error.
chip select
The signal that picks one device on a shared bus, usually active low: an SPI peripheral listens and drives MISO only while its chip select is asserted.
framing error
A UART frame whose stop bit was received as 0 instead of 1, usually because the two ends run at rates too far apart or the receiver lost the frame's start.
loopback test
A test that connects a design's transmitter to its own receiver. It shows that the two halves agree with each other, but not that either meets the specification, since a mistake both share cancels out.
oversampling
Sampling a signal several times per bit, such as 16 times for a UART receiver, so that the receiver can find an edge and then the middle of each bit without the sender's clock.
phase accumulator
A register that adds a fixed step every clock cycle and gives a tick on each carry out of its top bit, so ticks come on average at step / 2^n of the clock rate, a fraction no whole-number divider can reach.
SPI (serial peripheral interface)
A synchronous serial bus with no formal standard: a controller drives a clock and a chip select, and on every clock period one bit goes each way, on MOSI to the peripheral and on MISO back.
SPI mode
One of four timings of an SPI bus, numbered with CPOL as the high bit and CPHA as the low bit: CPOL is the level the clock rests at, CPHA chooses whether data is sampled on the leading or the trailing edge of each bit.
UART (universal asynchronous receiver-transmitter)
A serial link with no shared clock: each byte travels as a frame of a start bit of 0, the data bits least significant first, an optional parity bit and a stop bit of 1, on a line that idles at 1.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.