CRAFT cycle · 2.5 hours · Wed 6/10 · PM: Park Güell · UART condensed
HDL for Digital System Design · UCF ECE · Barcelona Summer 2026
| Phase | Time | Activity |
|---|---|---|
| 🌍 Contextualize | 10 min | Serial everywhere · Go Board USB-UART · Semidynamics debug |
| ⚠️ Reframe | 15 min | There is no print() · FSM + PISO + baud counter |
| 🛠 Assemble | 70 min | UART TX (sim) · single char on button · "HELLO" string · hex-to-ASCII stretch |
| 🛡 Fortify | 45 min | Waveform vs spec · scope/logic-analyzer the wire · 🤖 protocol-aware TB |
| 🔗 Transfer | 10 min | RX preview · Park Güell bridge · project build day next |
▸ Phase 1 of 5 · ~10 min
Every chip you've touched in Barcelona talks serial
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
There is no print(). There is a shift register.
"printf("HELLO"). Done. The hardware will figure it out."
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.
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 ────────────▶
// 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.
| Baud | CLKS_PER_BIT |
|---|---|
| 9600 | 2604 |
| 19200 | 1302 |
| 57600 | 434 |
| 115200 | 217 |
parameter CLKS_PER_BIT means it's a parameter override.
▸ Phase 3 of 5 · ~70 min · You build
Sim first · then "HELLO" on your laptop
CLKS_PER_BIT=4 for fast sim. Verify the 'A' (0x41) frame in GTKWave.tx_data='A', pulse tx_start, route tx_out to the pin. Open terminal → see 'A'.tx_busy low before advancing.0A\r\n", "0B\r\n". CLKS_PER_BIT cycles before advancing. The DATA state loops 8 times, one bit per period. This is the FSM the skeleton below implements.
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
// 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
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
If the terminal shows garbage, the bug is timing
tx_start.CLKS_PER_BIT cycles. Measure in the waveform.0x41 = 0100_0001, the order on the wire is 1,0,0,0,0,0,1,0.tx_busy drops.CLKS_PER_BIT cycles, start to stop end.CLKS_PER_BIT, (b) MSB instead of LSB, or (c) terminal set to 9600 when you're sending 115200.
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."
tx_busy contract only matters when you push two bytes in a row.▸ Phase 5 of 5 · ~10 min · Park Güell · then build your project
Protocols generalize. This afternoon: Gaudí's modular tiles.
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.
parameter CLKS_PER_BIT and generate blocks.🔗 End of Day 11 · Olé
FSM + counter + shift register + a parameter for baud.
Tomorrow: a project build day — put it all to work.