Module 10: Capstone Projects · Project 2: UART Sequential Flow

Project 2, Part 1: Setup and Clocking Strategy

Project 1 had no clock, so it never tested the part of the flow that most often causes trouble in real projects. This one does. The UART controller from the Verilog course’s Module 10, uart_top, with its transmitter, receiver and their baud tick generators, has real flops, a real clock and a real reason to care about skew. That is not a complication to work around; it is why I chose it.

This is a fresh run of that UART with its own configuration. It is not the same netlist as the UART used as the running example in Modules 5 to 9, which had extra parity logic and a much tighter 4 ns clock, so do not expect the numbers to match.

The design

For physical design, uart_top gets its real interface instead of the loopback wiring used in the testbench: a separate tx_line output and rx_line input.

module uart_top #(
    parameter CLK_FREQ  = 100_000_000,
    parameter BAUD_RATE = 115200
) (
    input  clk, rst_n,
    input  [7:0] tx_data_in,
    input        tx_start,
    output       tx_busy,
    output       tx_line,
    input        rx_line,
    output [7:0] rx_data_out,
    output       rx_valid,
    output       rx_framing_error
);

CLK_FREQ and BAUD_RATE are not just simulation parameters. They set the divide ratios of the baud tick generators, and so the width of their counters and compare logic.

Choosing the clock

The Verilog course verified this design at 50 MHz. Nothing in the RTL ties it to that frequency, as long as CLK_FREQ matches whatever the physical design targets, and pushing a design beyond the frequency it was first verified at is a normal way to find out how much margin it has. This project targets 100 MHz:

$$ T_{clk} = \frac{1}{100\ \text{MHz}} = 10\ \text{ns} $$

The config

{
    "DESIGN_NAME": "uart_top",
    "VERILOG_FILES": "dir::src/uart_top.v",
    "CLOCK_PORT": "clk",
    "CLOCK_PERIOD": 10,
    "PNR_SDC_FILE": "dir::uart_top.sdc",
    "SIGNOFF_SDC_FILE": "dir::uart_top.sdc",
    "FP_SIZING": "relative",
    "FP_CORE_UTIL": 45,
    "PL_TARGET_DENSITY": 0.55,
    "RUN_POST_CTS_RESIZER_TIMING": false
}

CLOCK_PORT is a real net this time. The last line is unusual and deliberate: OpenLane normally runs the resizer’s timing repair straight after CTS and fixes small hold violations without you ever seeing them. For this project I have switched that step off, so that we can look at a hold violation and fix it ourselves in the next article. On a real project you would leave it on. (Variable names here are from OpenLane 2; check the documentation of your version.)

The SDC

create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty 0.25 [get_clocks clk]
set_clock_transition 0.15 [get_clocks clk]

set_input_delay  1.5 -clock clk [get_ports {tx_data_in[*] tx_start rx_line}]
set_output_delay 1.5 -clock clk [get_ports {tx_busy tx_line rx_data_out[*] rx_valid rx_framing_error}]

set_driving_cell -lib_cell sky130_fd_sc_hd__buf_2 -pin X [all_inputs]
set_load 0.03 [all_outputs]

A real clock this time, with a real set_clock_uncertainty. Before CTS, that number stands in for the skew and jitter of a clock tree that does not exist yet. After CTS the real tree’s skew is computed, and the uncertainty is left to cover jitter and margin. Module 2.4 introduced the command; this project is where it stops being abstract.

Synthesis

openlane --flow Classic --run-tag uart_run1 --to Yosys.Synthesis designs/uart/config.json
=== uart_top ===

   Number of cells:                431
     sky130_fd_sc_hd__dfxtp_1       62
     sky130_fd_sc_hd__mux2_1        38
     sky130_fd_sc_hd__nand2_1       57
     sky130_fd_sc_hd__a21oi_1       29
     ...

   Chip area for module '\uart_top': 5391.4

62 flops: the two baud counters, both shift registers, the received data register, the bit counters, both state machines and a few status bits. The synthesis step’s STA, run against an ideal clock where every flop sees the clock edge at exactly the same time, reports a worst setup slack of about +6 ns. That is a real number, but it says little about timing after CTS, because an ideal clock is the one assumption a real clock tree can never meet. The next article replaces the ideal clock with a real one and shows how much of that margin survives, and what happens to hold.

Floorplan

openlane --flow Classic --run-tag uart_run1 --from OpenROAD.Floorplan --to OpenROAD.Floorplan \
         designs/uart/config.json
Core area: 109.940 x 109.140 um
Number of rows: 40

About 5,400 µm² of cells at 45% gives a core of about 12,000 µm², 110 µm on a side. Nothing dramatic. For this design, floorplanning and placement are not where the difficulty is.

That is a very common shape for a real project: a small or medium sequential block where the floorplan is nearly a formality and most of the engineering effort goes into CTS and timing closure. Knowing where to expect the work before you get there is half the job.

Practice Exercise

The baud tick generator divides the 100 MHz clock to produce a 115200 baud tick with a counter and a compare. With CLK_FREQ = 100_000_000 and BAUD_RATE = 115200, what divide count does it need (rounded to the nearest integer), and what baud rate error does the rounding introduce?

Solution

$$ N = \frac{100{,}000{,}000}{115200} \approx 868.06 $$

Rounding to $N = 868$ gives $100{,}000{,}000 / 868 \approx 115{,}207$ baud, an error of about 0.006%, far inside any UART’s tolerance of a few percent. (If the receiver samples at 16 times the baud rate, the divide count is $100{,}000{,}000 / (16 \times 115200) \approx 54.25$, rounded to 54, an error of about 0.47%, still comfortable.) The divide count sets the counter width, 10 bits for 868, and so the size of the compare logic sitting between the counter flops, which is where the next article’s hold violation shows up.

Synthesis and floorplanning were unremarkable, which sets up what comes next. In the next article we run placement, CTS and routing, meet a real hold violation after CTS, and fix it.

← Project 1, Part 3: Signoff and Retrospective Project 2, Part 2: CTS and Timing Closure →