Barcelona Abroad · Week 3 · Day 11  📡 First protocol on the wire

UART: Protocol Design & Implementation

CRAFT cycle · 2.5 hours · Wed 6/10 · PM: Park Güell · UART condensed

HDL for Digital System Design · UCF ECE · Barcelona Summer 2026

CRAFT

Today at a Glance

PhaseTimeActivity
🌍 Contextualize10 minSerial everywhere · Go Board USB-UART · Semidynamics debug
⚠️ Reframe15 minThere is no print() · FSM + PISO + baud counter
🛠 Assemble70 minUART TX (sim) · single char on button · "HELLO" string · hex-to-ASCII stretch
🛡 Fortify45 minWaveform vs spec · scope/logic-analyzer the wire · 🤖 protocol-aware TB
🔗 Transfer10 minRX preview · Park Güell bridge · project build day next
Condensed day: baseline D11+D12 (UART TX + RX + SPI) compressed to one session. Full TX, conceptual RX, SPI dropped.

▸ Phase 1 of 5  ·  ~10 min

🌍 Contextualize

Every chip you've touched in Barcelona talks serial

Serial Is Everywhere

  • Your Go Board — the USB cable in your laptop right now is hosting a USB-to-UART bridge. Today you put real bytes on it.
  • Semidynamics RISC-V chips — every silicon bring-up starts with a UART debug port. No UART, no boot log, no chip.
  • Barcelona Metro telemetry — train-to-trackside status uses serial protocols layered on top of UART-style framing.
  • HP printers — head/cartridge handshake = a tiny embedded UART.

The hardware engineer's first ritual

Power on the chip → wire up TX/RX → open a terminal → see boot…. That's the moment your design is alive. Today you build the transmitter half of that ritual.

UART is 50 years old. It's the most-deployed protocol on Earth that isn't TCP/IP. You're learning the lingua franca.

▸ Phase 2 of 5  ·  ~15 min

⚠️ Reframe

There is no print(). There is a shift register.

⚠️ "Just Send a String"

❌ Wrong Model

"printf("HELLO"). Done. The hardware will figure it out."

✓ Right Model

You are building, in silicon, the thing that implements print: a baud counter + bit counter + PISO shift register + 5-state FSM. There is no software underneath.

UART TX = FSM that shifts bits at a fixed rate. The complexity is in the timing, not the logic.

The Four Blocks Inside "print"

UART TX block diagram: a control FSM drives a baud-rate counter, a bit counter, and a PISO shift register that serializes the latched data byte onto the tx_out line
Baud counter (when to shift) · bit counter (which bit) · PISO shift register (the data) · FSM (the sequence). These are the four modules you build below.

The Frame on the Wire

UART frame timing: line idles high, drops low for one start bit, sends 8 data bits LSB-first, then returns high for the stop bit
  IDLE   START   D0   D1   D2   D3   D4   D5   D6   D7   STOP   IDLE
   1       0     b0   b1   b2   b3   b4   b5   b6   b7    1      1
  ─────┐  ┌──┐  ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐  ┌────────
       │  │  │  │  │ │  │ │  │ │  │ │  │ │  │ │  │ │  │  │
       └──┘  └──┘  └─┘  └─┘  └─┘  └─┘  └─┘  └─┘  └─┘  └──┘
                ◀──────────── 8 data bits, LSB first ────────────▶
  • IDLE = line high. Always.
  • START = 1 bit-period low. The falling edge is the receiver's only synchronization point — it sets the clock for the whole frame.
  • DATA = 8 bits, LSB first. (This is the bug everyone hits at least once.)
  • STOP = 1 bit-period high. Returns the line to idle.

Baud Math

// 25 MHz Go Board clock
// 115200 baud
parameter CLKS_PER_BIT = 25_000_000 / 115_200;
//                     = 217.01… ≈ 217

Actual baud = 25e6 / 217 = 115,207 Hz. Error = 0.006%. Well within the ±5% UART tolerance.

Common rates @ 25 MHz

BaudCLKS_PER_BIT
96002604
192001302
57600434
115200217
Parameterize it. Hard-coded 217 means a baud change is an RTL change. parameter CLKS_PER_BIT means it's a parameter override.

▸ Phase 3 of 5  ·  ~70 min  ·  You build

🛠 Assemble

Sim first · then "HELLO" on your laptop

Build Plan

  1. Ex 1 · 40 min  UART TX in simulation — FSM (IDLE → START → DATA → STOP) + baud counter + bit counter. Testbench at CLKS_PER_BIT=4 for fast sim. Verify the 'A' (0x41) frame in GTKWave.
  2. Ex 2 · 20 min  Single character on button — debounce (reuse D5 module), drive tx_data='A', pulse tx_start, route tx_out to the pin. Open terminal → see 'A'.
  3. Ex 3 · 25 min  "HELLO\r\n" — 7-byte ROM + sequencer FSM that waits for tx_busy low before advancing.
Stretch (Ex 4): hex-to-ASCII — transmit a counter value as "0A\r\n", "0B\r\n".
Dropped from required: UART RX (pre-class video covers it) · SPI master.

The TX FSM — IDLE → START → DATA → STOP

UART TX state machine: IDLE waits for tx_start, START holds the line low one bit-period, DATA shifts 8 bits one per bit-period, STOP holds high one bit-period, then returns to IDLE
Each state holds for CLKS_PER_BIT cycles before advancing. The DATA state loops 8 times, one bit per period. This is the FSM the skeleton below implements.

UART TX (Skeleton)

module uart_tx #(parameter CLKS_PER_BIT = 217) (
    input  wire       clk, rst,
    input  wire       tx_start,
    input  wire [7:0] tx_data,
    output reg        tx_out,
    output reg        tx_busy
);
    localparam IDLE=3'd0, START=3'd1, DATA=3'd2, STOP=3'd3;
    reg [2:0]  state;
    reg [15:0] baud_cnt;
    reg [2:0]  bit_idx;
    reg [7:0]  data_latched;

    always @(posedge clk) begin
        if (rst) begin state <= IDLE; tx_out <= 1; tx_busy <= 0; end
        else case (state)
            IDLE:  begin tx_out <= 1; tx_busy <= 0;
                         if (tx_start) begin
                             data_latched <= tx_data; tx_busy <= 1;
                             baud_cnt <= 0; state <= START;
                         end end
            START: begin tx_out <= 0;                       // start bit
                         if (baud_cnt == CLKS_PER_BIT-1) begin
                             baud_cnt <= 0; bit_idx <= 0; state <= DATA;
                         end else baud_cnt <= baud_cnt + 1; end
            DATA:  begin tx_out <= data_latched[bit_idx];   // LSB first
                         if (baud_cnt == CLKS_PER_BIT-1) begin
                             baud_cnt <= 0;
                             if (bit_idx == 7) state <= STOP;
                             else              bit_idx <= bit_idx + 1;
                         end else baud_cnt <= baud_cnt + 1; end
            STOP:  begin tx_out <= 1;                       // stop bit
                         if (baud_cnt == CLKS_PER_BIT-1) state <= IDLE;
                         else baud_cnt <= baud_cnt + 1; end
        endcase
    end
endmodule

"HELLO" Sequencer

// 7-byte ROM in initial
reg [7:0] msg [0:6];
initial begin
    msg[0]="H"; msg[1]="E"; msg[2]="L";
    msg[3]="L"; msg[4]="O"; msg[5]="\r"; msg[6]="\n";
end

// Walk the ROM, wait for !tx_busy between bytes
always @(posedge clk) case (s)
    LOAD: begin tx_data <= msg[idx]; tx_start <= 1; s <= WAIT; end
    WAIT: begin tx_start <= 0;
                if (!tx_busy && tx_busy_d) begin           // falling edge
                    if (idx == 6) s <= DONE;
                    else begin idx <= idx + 1; s <= LOAD; end
                end end
endcase
The classic bug: firing tx_start while tx_busy is still high. The TX engine ignores you and your second byte never goes out. Always wait for the falling edge of tx_busy.

▸ Phase 4 of 5  ·  ~45 min  ·  Sim · scope · AI

🛡 Fortify

If the terminal shows garbage, the bug is timing

Waveform vs Spec — Check Every Bit

  • IDLE — line high for at least 1 cycle before tx_start.
  • START — line low for exactly CLKS_PER_BIT cycles. Measure in the waveform.
  • DATA bit 0 first — for 'A' = 0x41 = 0100_0001, the order on the wire is 1,0,0,0,0,0,1,0.
  • STOP — line high for one full bit period. Then tx_busy drops.
  • Total frame = 10 × CLKS_PER_BIT cycles, start to stop end.
If garbage on the terminal: 99% of the time it's (a) wrong CLKS_PER_BIT, (b) MSB instead of LSB, or (c) terminal set to 9600 when you're sending 115200.

🤖 Check the Machine — Protocol-Aware TB

Prompt: "Write a Verilog testbench for a UART TX module with CLKS_PER_BIT=4. The TB should verify start bit, 8 data bits LSB-first, stop bit, busy semantics, and back-to-back transmissions. Use self-checking with PASS/FAIL output."

  • Did it measure bit duration? Many AI TBs just check value, not duration. A frame that's the right bits at the wrong rate is a fail in the real world.
  • Did it check LSB-first? Watch for an MSB-first assumption — it'll silently "pass" wrong code.
  • Did it test back-to-back TX? The tx_busy contract only matters when you push two bytes in a row.
  • Annotate corrections, save to portfolio. This is the kind of protocol-aware testbench your project's verification builds on.

Hardware Verification

  • Single char: button → 'A' on terminal. Hold the button — does it spam or pulse once? (Debouncer test.)
  • "HELLO": press → "HELLO\r\n" appears. Press again — second copy appears on a new line. Now you have a printer.
  • Scope/logic analyzer the TX line (if available) — measure 115.2 kHz bit rate directly. Confirm your math.
  • Mismatch demo — set the terminal to 9600 baud. Same "HELLO" press. Watch the garbage. Now you've felt a baud mismatch in your bones.
UART RX — covered in pre-class video, available as stretch. The pattern (16× oversample, frame detect) mirrors TX. Optional today; required for any project that needs two-way comms.

▸ Phase 5 of 5  ·  ~10 min  ·  Park Güell · then build your project

🔗 Transfer

Protocols generalize. This afternoon: Gaudí's modular tiles.

Where This Goes

Tomorrow (Thu 6/11): a structured project build + lab catch-up day. Your UART skills go straight into your project — integrate, debug, and start the core module.

Project use: Option 1 (UART Command Parser) is a direct extension of today. Even projects that don't need TX get a debug UART for free.

This afternoon: Park Güell

  • 🦎 Gaudí's tilework is a modular, parameterized system — the same tile repeated with variation, exactly like your parameter CLKS_PER_BIT and generate blocks.
  • 🛠 No new video — tomorrow is hands-on project time. Keep your core module moving toward the Thu 6/11 milestone.
Reflection prompt: in one sentence — what's the smallest change to your UART that would make it talk SPI, I²C, or RS-485? (SPI & UART RX live in the optional "Keep Going" self-study track.)

🔗 End of Day 11 · Olé

You printed "HELLO" from silicon.
No software underneath.

FSM + counter + shift register + a parameter for baud.
Tomorrow: a project build day — put it all to work.

CRAFT