Module 5: Clock Tree Synthesis ยท 5.3 CTS Algorithms

Running CTS on the UART

Everything up to this point has been preparation. This article runs it.

The starting point is uart_top, already carried through synthesis, floorplanning, and global and detailed placement: a legal, placed netlist in standard cell rows, with every flop’s location fixed and known. That is exactly the input TritonCTS needs, real sink locations to cluster and build a tree around.

Confirming the config, then running

Assume designs/uart_top/config.json already carries the CTS section from two articles ago: target skew, tolerance, clustering parameters and the buffer list. From OpenLane’s interactive flow:

% run_cts
[INFO]: Building RC values for the design based on the extraction rules...
[INFO]: Root buffer selected: sky130_fd_sc_hd__clkbuf_16
[INFO]: Clustering 58 clock sinks...
[INFO]: Sink clustering size: 25, max diameter: 50um
[INFO]: Number of clusters formed: 6
[INFO]: Inserting buffered clock tree...
[INFO]: TritonCTS placement adjustment complete.
[INFO]: Clock tree synthesis complete.

That log is representative of the shape of TritonCTS’s output, not a verbatim transcript, but every line corresponds to a phase this module has already covered. The 58 sinks are the UART’s flops: baud counters in u_tx_baud and u_rx_baud, the transmitter and receiver FSM state registers, shift registers and status flags. They are clustered per the clustering knobs, then buffered bottom-up into a tree rooted at a clkbuf_16, the strongest variant in the config, chosen because the root has to drive the entire downstream tree.

Reading the CTS summary

After the stage completes OpenLane writes a summary. A representative version for this run:

Clock Tree Synthesis Summary
=============================
Clock net                     : clk
Number of clock roots         : 1
Number of clock sinks         : 58
Number of clock subnets       : 21
Number of buffers inserted    : 18
Number of tree levels         : 3
Max buffer level fanout       : 12

Latency (min / max / skew)
  Min latency                 : 0.298 ns
  Max latency                 : 0.409 ns
  Achieved worst-case skew    : 0.111 ns

Target skew                   : 0.150 ns
Tolerance                     : 0.100 ns
Status                        : MET (achieved skew within tolerance of target)

Walk it the way you would walk any report by now. 58 sinks confirms every UART flop was connected into the tree and nothing was orphaned. 18 buffers across 3 levels means a root buffer, an intermediate level and cluster-level buffers nearest the leaves, a shallow tree consistent with a design this size. The achieved worst-case skew of 0.111 ns sits inside the 0.150 ns target plus 0.100 ns tolerance, so the stage reports MET.

Hold onto that 0.111 ns. It is within target and the run reports MET, so CTS did its job by its own criterion. Section 5.4 is going to show, using this exact value, that “met the skew target” and “no new timing violations” are two different claims. A tree can be a good tree by CTS’s own measure and still hand the design a brand-new hold violation.

Viewing the inserted tree

Open the post-CTS DEF in Magic or KLayout the same way Module 4 opened the post-placement DEF:

magic -rcfile sky130A.magicrc -T sky130A.tech uart_top.cts.def &

Where the post-placement view showed only logic cells in rows, the post-CTS view adds a new, visually distinct population: the clock buffers this run inserted, sky130_fd_sc_hd__clkbuf_*. Select the clk net in the viewer’s net highlighting and you can trace it: root buffer near the clock pin, fanning out through the intermediate level, ending at the buffers nearest each cluster of flops. This is the first point in the flow where the abstract clock tree from every earlier article becomes something you can point at in a layout. Real flows lean on this. Clock buffers get distinct names, and often a dedicated region, precisely so a person reviewing the layout can tell “this cell distributes the clock” from “this cell implements logic” at a glance.

Practice Exercise

A different design’s CTS summary reports 140 sinks, 4 tree levels, achieved worst-case skew of 0.240 ns, a target of 0.150 ns and a tolerance of 0.050 ns. Did the run meet its target?

Solution

No. Target plus tolerance is 0.150 + 0.050 = 0.200 ns, and the achieved 0.240 ns exceeds that. The run reports a status other than MET, and the next step would be to find out why: commonly clustering that is too coarse, a target too strict for this design’s sink spread, or a floorplan with a genuinely awkward sink distribution that a flat tree struggles to balance.

The UART has a real, verified clock tree. Section 5.4 takes the step every real flow takes immediately after CTS: rerun timing, and confront the fact that hold violations characteristically show up for the first time right here.

โ† Useful Skew Why Hold Violations Appear After CTS โ†’