Project 15: UART Transmitter
Transmit bytes using UART framing with a baud generator.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 1 week |
| Main Programming Language | Verilog (Alternatives: VHDL, SystemVerilog) |
| Alternative Programming Languages | VHDL, SystemVerilog |
| Coolness Level | High |
| Business Potential | Medium |
| Prerequisites | Shift registers, Counters, Serial protocols |
| Key Topics | UART framing, Baud generator |
1. Learning Objectives
- Generate UART frames in hardware
- Compute baud divisors from clock
- Expose busy/ready signals
2. All Theory Needed (Per-Concept Breakdown)
UART Framing and Baud Generation
Description/Expanded Explanation of the concept
UART is asynchronous serial communication. Each byte is framed with a start bit, data bits, optional parity, and stop bit. A baud generator creates a tick to advance the TX or RX state.
Definitions & Key Terms
- Baud -> symbols per second
- Start bit -> logic 0 indicating frame start
- Stop bit -> logic 1 indicating frame end
Mental Model Diagram (ASCII)
idle(1) start(0) d0 d1 d2 d3 d4 d5 d6 d7 stop(1)

How It Works (Step-by-Step)
- Compute divisor from clock to target baud.
- Generate a baud tick.
- Shift out bits at each tick.
Minimal Concrete Example
if (baud_tick) begin
tx <= shift[0];
shift <= {1'b1, shift[9:1]};
end
Common Misconceptions
- “UART is clocked.” -> It is asynchronous.
- “Baud doesn’t need to be exact.” -> Too much error breaks framing.
Check-Your-Understanding Questions
- What does 8N1 mean?
- How do you compute a baud divider?
- Why is idle high?
Where You’ll Apply It
- This project: used in Section 3.2 and Section 5
- Also used in: P16-uart-receiver.md
Shift Registers
Description/Expanded Explanation of the concept
Shift registers move data one bit per clock. They are the core of serial protocols and LED chasers. They can shift left, right, or rotate, and they are often paired with counters for timing.
Definitions & Key Terms
- Shift register -> register that shifts bits each cycle
- Serial -> one bit per clock
- Parallel -> multiple bits at once
Mental Model Diagram (ASCII)
[ b7 b6 b5 b4 b3 b2 b1 b0 ] -> shift -> [ b6 ... b0 new ]

How It Works (Step-by-Step)
- Load a register with a value.
- On each clock edge, shift left or right.
- Insert a new bit at the shifted end.
Minimal Concrete Example
shift <= {shift[6:0], serial_in};
Common Misconceptions
- “Shift registers are only for serial I/O.” -> They are also for patterns and delays.
- “Shifts are free.” -> They still consume registers and routing.
Check-Your-Understanding Questions
- What is the difference between shift and rotate?
- How many cycles to shift out 8 bits?
- Why use shift registers in UART TX?
Where You’ll Apply It
- This project: used in Section 3.2 and Section 4
- Also used in: P15-uart-transmitter.md, P17-spi-master.md
Verification with Testbenches and Waveforms
Description/Expanded Explanation of the concept
Testbenches are simulation-only modules that apply stimulus and check outputs. Waveforms (VCD) are the hardware engineer’s microscope; they reveal timing, glitches, and ordering problems. A good testbench is deterministic and covers edge cases.
Definitions & Key Terms
- Testbench -> a non-synthesizable module that drives a DUT
- VCD -> Value Change Dump waveform file
- Deterministic test -> same inputs produce same outputs every run
Mental Model Diagram (ASCII)
[Testbench] -> [DUT] -> [VCD] -> [GTKWave]

How It Works (Step-by-Step)
- Initialize inputs to known values.
- Apply stimulus over time.
- Dump waveforms and check outputs.
- Add assertions or PASS/FAIL messages.
Minimal Concrete Example
initial begin
$dumpfile("wave.vcd");
$dumpvars(0, tb);
a = 0; b = 1; #10;
$finish;
end
Common Misconceptions
- “If it simulates once, it’s correct.” -> Cover all relevant cases.
- “Waveforms are optional.” -> They are often the only way to debug timing.
Check-Your-Understanding Questions
- Why keep testbench and DUT separate?
- What is the purpose of
$dumpvars? - How do you make a testbench deterministic?
Where You’ll Apply It
- This project: used throughout Section 6 (testing)
- Also used in: all other projects in this folder
3. Project Specification
3.1 What You Will Build
A UART TX module supporting 8N1 at configurable baud.
3.2 Functional Requirements
- Requirement 1: Frame bytes with start/data/stop
- Requirement 2: Generate baud tick from clock
- Requirement 3: Provide busy/ready handshaking
3.3 Non-Functional Requirements
- Performance: Stable operation at the target clock and interfaces.
- Reliability: Deterministic outputs on all defined inputs.
- Usability: Clear ports and documented behavior.
3.4 Example Usage / Output
{p['example_usage']}
3.5 Data Formats / Schemas / Protocols
{p[‘data_format’]}
3.6 Edge Cases
- Back-to-back bytes
- Baud mismatch
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
vvp uart_tx_tb
3.7.2 Golden Path Demo (Deterministic)
Run the demo command above with the provided testbench and confirm the outputs match the golden transcript.
3.7.3 CLI Transcript
TX byte=0x55
frame: start 01010101 stop
3.7.4 Failure Demo (Expected)
# Example failure case
ERROR: Output mismatch at vector 3
Expected: 0x0A, Got: 0x0B
EXIT CODE: 1
Notes:
- Exit code 0 indicates all tests passed
- Exit code 1 indicates a test failure
4. Solution Architecture
4.1 High-Level Design
[inputs] -> [core logic] -> [outputs]

4.2 Key Components
| Component | Responsibility |
|---|---|
| baud_gen | Baud tick generator |
| tx_fsm | Frame sequencing |
4.3 Data Structures (No Full Code)
// Example signals (adapt to your design)
reg [7:0] state_reg;
reg [7:0] data_reg;
4.4 Algorithm Overview
Key Algorithm: Core control flow
- Initialize state/reset conditions.
- Apply inputs and compute outputs.
- Update state on clock edges (if sequential).
Complexity Analysis:
- Time: O(1) per cycle
- Space: O(N) for registers and logic
5. Implementation Guide
5.1 Development Environment Setup
iverilog -v
# Ensure GTKWave is installed for waveform viewing
5.2 Project Structure
project-root/
|-- src/
| |-- top.v
| |-- core.v
|-- tb/
| |-- tb.v
|-- Makefile
|-- README.md

5.3 The Core Question You’re Answering
“How do you serialize data into a real-world asynchronous protocol?”
5.4 Concepts You Must Understand First
- Shift registers
- Counters
- Serial protocols
5.5 Questions to Guide Your Design
- What baud rate and clock are you using?
- How do you signal ready for next byte?
5.6 Thinking Exercise
Compute the divider for 115200 baud at 50 MHz.
5.7 The Interview Questions They’ll Ask
- What does 8N1 mean?
- Why is the line idle high?
5.8 Hints in Layers
- Load a shift register with start/data/stop.
- Shift on each baud tick.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Serial protocols | Making Embedded Systems | Ch. 8 |
5.10 Implementation Phases
Phase 1: Foundation
Goals:
- Establish core module structure
- Implement minimal behavior
Tasks:
- Scaffold module ports and internal signals
- Write a minimal testbench that compiles
Checkpoint: Simulation runs without errors
Phase 2: Core Functionality
Goals:
- Implement full logic
- Verify edge cases
Tasks:
- Complete core logic
- Add directed tests for edge cases
Checkpoint: All tests pass and waveforms match expectations
Phase 3: Polish & Edge Cases
Goals:
- Improve readability
- Document behavior
Tasks:
- Add comments and README notes
- Expand tests for unusual inputs
Checkpoint: Design is deterministic and documented
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Reset strategy | Sync / Async | Sync | Simpler timing closure |
| Test coverage | Directed / Exhaustive | Exhaustive for small logic | Prevents missed cases |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Test core logic | Small vectors |
| Integration Tests | Test modules together | Full system |
| Edge Case Tests | Boundary conditions | Max/min values |
6.2 Critical Test Cases
- Test 1: Check bit width vs baud
- Test 2: Back-to-back frame testing
6.3 Test Data
Use deterministic vectors and document expected outputs.
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Wrong baud | Frame too fast/slow | Fix divider calculation |
7.2 Debugging Strategies
- Inspect waveforms at key internal signals
- Add temporary debug outputs to verify state
- Reduce testcases to the smallest failing case
7.3 Performance Traps
- Overly wide counters or combinational paths can reduce max clock
8. Extensions & Challenges
8.1 Beginner Extensions
- Add parameterization for widths
- Add optional features (enable, reset)
8.2 Intermediate Extensions
- Add configuration registers
- Build a simple driver or demo program
8.3 Advanced Extensions
- Integrate with another project in this series
- Implement a hardware demo on FPGA
9. Real-World Connections
9.1 Industry Applications
- Digital control systems and embedded peripherals
- FPGA prototyping and validation
9.2 Related Open Source Projects
- Yosys / nextpnr toolchain for open-source FPGA flow
- Example HDL projects in the FPGA community
9.3 Interview Relevance
- Demonstrates RTL thinking and verification skills
10. Resources
10.1 Essential Reading
- Making Embedded Systems - Focus on Ch. 8
10.2 Video Resources
- Search for project-specific HDL walkthroughs and waveforms
10.3 Tools & Documentation
- Icarus Verilog
- GTKWave
10.4 Related Projects in This Series
- See adjacent projects in
VERILOG_FROM_ZERO_PROJECTS/
11. Self-Assessment Checklist
11.1 Understanding
- I can explain the core concept without notes
- I can predict waveform behavior for basic inputs
11.2 Implementation
- All functional requirements are met
- All tests pass
- Edge cases are documented
11.3 Growth
- I can explain this project in an interview
- I documented at least one lesson learned
12. Submission / Completion Criteria
Minimum Viable Completion:
- Functional requirements implemented
- Testbench passes
- Waveforms inspected
Full Completion:
- All minimum criteria plus
- Edge cases covered and documented
Excellence (Going Above & Beyond):
- Hardware demo on FPGA
- Clear write-up of lessons learned
Appendix A: Deep Dive Walkthrough
A.1 Signal Map and Timing Contract
- Inputs:
clk,reset,tx_start,tx_data[7:0] - Outputs:
tx,tx_busy
Invariant: When tx_start is accepted, tx_busy stays high for exactly 10 bit periods (start + 8 data + stop).
A.2 Baud Divider Example (50 MHz -> 115200)
- Divider = 50,000,000 / 115,200 = 434.027…
- Use 434 for a small positive error (0.006%).
A.3 Frame Timeline
Idle(1) | Start(0) | d0 d1 d2 d3 d4 d5 d6 d7 | Stop(1)

A.4 Deterministic Test Vectors
- Send
0x55-> expect01010101LSB-first. - Send
0xA5-> expect10100101LSB-first.
A.5 Debugging Tip
If the receiver sees framing errors, verify the stop bit is high and lasts exactly one bit period.
13. Deep Dive Appendix
13.1 Timing and Resource Budget
- Baud divisor = clk_hz / baud. Example: 50 MHz / 115200 = 434.027.
- Choose integer divisor 434 and accept about 0.006% error.
- Total frame time for 8N1 is 10 bit times.
13.2 Waveform Interpretation Guide
- tx should idle high, then go low for start, then LSB-first data, then stop high.
- Verify busy remains high for exactly 10 baud ticks per byte.
Example:
byte=0x55 -> 0 1 0 1 0 1 0 1 0 1 (start, data, stop)
13.3 Hardware Bring-Up Notes
- Connect TX to a USB-serial adapter (3.3V logic).
- Use a terminal at the matching baud to verify output.
- If output looks garbled, check baud divider and bit order.
13.4 Alternate Implementations and Trade-offs
- Fractional baud generator for improved accuracy.
- Add parity or 9-bit support for protocols like MODBUS.
- Add a TX FIFO to decouple producer and transmitter.
13.5 Additional Exercises
- Add a break condition (hold TX low for more than 1 frame).
- Implement a command frame with checksum.
- Add flow control (CTS/RTS) as optional signals.