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.