Logic Synthesis with Yosys
"Synthesis" is one word for four quite different transformations: elaborating the language, inferring structure, optimising Boolean logic, and covering that logic with real cells from a real library. Yosys exposes each of them as a separate command, which makes it the best teaching tool in the industry - you can stop after any pass and look at what it did.
2.1 The synthesis pipeline, pass by pass
Commercial tools hide this behind a single compile. Yosys does not, and running
the passes by hand once is worth more than any amount of reading.
# A synthesis script written out by hand. OpenLane runs the equivalent,
# but reading it once removes all the mystery.
read_verilog -sv rtl/counter.sv
# Elaborate. -check errors on a module that was never defined, which is
# the difference between a black box and a typo.
hierarchy -check -top counter
# always blocks become multiplexers and generic flip-flops.
proc
opt
# Recognise higher-level structure BEFORE flattening it into gates.
fsm ;# extract and re-encode state machines
opt
memory ;# arrays become $mem cells
opt
# Generic arithmetic ($add, $mul) into generic gates.
techmap
opt
# Map flip-flops to real library cells. This MUST precede abc, because
# abc is a combinational engine and does not understand state.
dfflibmap -liberty $::env(LIB_TYPICAL)
# Combinational optimisation + technology mapping.
# -D is a delay target in PICOseconds. Omit it and you get an
# area-optimised netlist that will not meet timing.
abc -liberty $::env(LIB_TYPICAL) -D 10000
# Housekeeping that matters more than it looks.
setundef -zero ;# no x in the netlist - x is not a value in silicon
splitnets -ports ;# one net per bit, so LVS can match names
opt_clean -purge ;# delete everything nothing drives
# What did we get?
stat -liberty $::env(LIB_TYPICAL)
check -assert ;# fail the run on a real structural problem
write_verilog -noattr results/counter.synth.v
fsm and memory run before techmap.
Once a state machine has been flattened into anonymous gates, no pass can recover it, so
the chance to re-encode one-hot is gone. dfflibmap runs before
abc. ABC is purely combinational; if flops are still generic when it
runs, it will either fail or leave them unmapped, and you will find $_DFF_P_
cells in a netlist that was supposed to contain only Sky130 cells.
2.2 Technology mapping with ABC
ABC is where your Boolean logic meets the library. It works in three steps, and the middle one is the interesting part.
- Convert to an AIG. Every gate is rewritten as AND gates and inverters - an and-inverter graph. A NAND becomes an AND plus an inverter, an XOR becomes a small AND/inverter subgraph. This canonical form has exactly two node types, which makes the optimisation algorithms tractable.
- Restructure the graph. Rewriting, refactoring and balancing move logic around to reduce either depth (for speed) or node count (for area). This is where a deeply nested expression gets flattened into a balanced tree.
- Cover it with real cells. Find a set of library cells whose functions, stitched together, implement the graph - at minimum cost, where cost is delay, area or a weighted blend depending on what you asked for.
Step three is why the library matters so much. A library with a rich set of complex cells - AOI, OAI, multiplexers, full adders - gives ABC bigger pieces to cover with, and bigger pieces mean fewer levels:
// Y = !((A & B) | C)
assign Y = ~((A & B) | C);
// The obvious mapping:
// and2_1 -> nor2_1
// two cells, two levels,
// ~10.0 um2, ~180 ps
// One AND-OR-INVERT cell computes
// exactly this function.
sky130_fd_sc_hd__a21oi_1 u (
.A1(A), .A2(B), .B1(C), .Y(Y)
);
// one cell, ONE level,
// ~6.3 um2, ~95 ps
//
// Complex cells are not a
// micro-optimisation. Across a
// large design they are 20-30%
// of both area and depth.
abc -D 10000 asks for a 10 ns critical path (the unit is picoseconds).
ABC will spend area to reach it - bigger drive strengths, more balanced trees, duplicated
logic. Ask for 1 ns on the same design and it will spend a great deal more area and
still miss. Ask for nothing and it optimises area alone. There is no "just make it good"
setting; you are always choosing a point on a curve, and -D is how you choose.
2.3 Drive strength, fanout and buffering
Choosing which cell implements a function is only half of mapping. The other half is choosing how big that cell should be - and this is the decision most directly connected to the Liberty tables from §1.3.
A inv_1 and an inv_8 compute the same function. The
inv_8 has roughly eight times the transistor width, so it drives a large load
far faster - but it also presents eight times the input capacitance to whatever drives
it, and occupies eight times the area. Upsizing a cell moves the problem upstream.
Two Liberty attributes turn this from an aesthetic preference into a hard rule that synthesis must satisfy - the design rule violations, or DRVs:
| Limit | Meaning | What happens if violated |
|---|---|---|
max_capacitance |
The largest load this pin may drive | Delay leaves the characterised table; the timing number is extrapolation |
max_transition |
The slowest edge allowed on this net | Same, plus real crowbar current in the receiving gate |
max_fanout |
A count-based proxy for the above | Cruder, but catches problems before load is known |
max_transition violations is not merely slow; it
dissipates measurably more power and, in the worst cases, ages faster. Fix DRVs before you
look at slack - they distort every timing number in the report anyway.
2.4 Reading the synthesis report honestly
stat is the first real feedback you get about your RTL. It is also routinely
misread, because the number everybody looks at is the least informative one.
=== counter ===
Number of wires: 1476
Number of cells: 1204
sky130_fd_sc_hd__a21oi_1 92
sky130_fd_sc_hd__and2_0 61
sky130_fd_sc_hd__buf_1 74
sky130_fd_sc_hd__buf_2 38
sky130_fd_sc_hd__clkbuf_1 16
sky130_fd_sc_hd__dfrtp_1 312 <-- flip-flops with reset
sky130_fd_sc_hd__dfxtp_1 48 <-- flip-flops without
sky130_fd_sc_hd__dlxtp_1 6 <-- LATCHES. investigate.
sky130_fd_sc_hd__inv_1 143
sky130_fd_sc_hd__mux2_1 97
sky130_fd_sc_hd__nand2_1 188
sky130_fd_sc_hd__nor2_1 112
sky130_fd_sc_hd__o21ai_1 17
Chip area for module '\counter': 10847.334400
| Line | What it really tells you |
|---|---|
| Chip area | The number people quote, and the least actionable. It is core cell area only - no routing, no power grid, no filler. Real die area is roughly this divided by your target utilisation (see Volume 03). |
| Flip-flop count | The honest size metric. Flops come directly from your RTL; combinational cell counts move with every optimisation setting. If flop count surprises you, your RTL is not what you think it is. |
Any dlxtp / dlatch |
Inferred latches. Almost always a bug - an incomplete always @(*). Track every one down. This is the same failure mode as Course 01, §1.5, now visible as real cells. |
| Buffer and inverter share | A design that is 25% buffers is fighting fanout somewhere. Look for a reset or enable driving thousands of loads. |
| Ratio of complex to simple cells | Lots of a21oi, o21ai, a22o means ABC found good coverings. Almost all nand2 and inv can mean your library is being under-used. |
Run check alongside stat. It finds structural problems that
stat cannot show: multiply-driven wires, combinational loops, cells with
unconnected inputs floating. With -assert it fails the run, which is what you
want in CI.
2.5 Constraints before synthesis
Synthesis without an SDC file produces a valid netlist that will never meet timing. With no clock defined there is no target, so the optimiser minimises area: minimum-drive cells, deep unbalanced logic, no buffering. It is not wrong - you simply never told it what to want.
# The minimum viable SDC for synthesis. Every line here changes the
# netlist you get out.
# ---- 1. the target ---------------------------------------------------
create_clock -name clk -period 10.0 [get_ports clk]
# Pessimism the tool should assume before CTS exists: jitter, plus the
# skew the clock tree has not been built to have yet. Drop this to a
# realistic number after CTS - see Volume 04.
set_clock_uncertainty 0.25 [get_clocks clk]
set_clock_transition 0.15 [get_clocks clk]
# ---- 2. the boundary -------------------------------------------------
# Without these, synthesis treats every I/O path as having a full cycle,
# which is almost never true and hides real failures.
set_input_delay -clock clk 3.0 [remove_from_collection [all_inputs] [get_ports clk]]
set_output_delay -clock clk 3.0 [all_outputs]
# ---- 3. the electrical environment -----------------------------------
# What drives our inputs, and what our outputs must drive. Omitting these
# means "an ideal zero-impedance source" and "no load", which is a lie
# that shows up as a timing surprise at chip assembly.
set_driving_cell -lib_cell sky130_fd_sc_hd__inv_2 -pin Y [all_inputs]
set_load 0.05 [all_outputs]
# ---- 4. design rules -------------------------------------------------
set_max_fanout 10 [current_design]
set_max_transition 1.5 [current_design]
# ---- 5. exceptions ---------------------------------------------------
# Same discipline as the FPGA course: an exception is a claim about the
# hardware, not a way to silence a failing path.
set_false_path -from [get_ports rst_n]
Interview grilling - "Synthesis met timing. Are you done?"
No, and the reasons are worth naming in order, because each one is a different stage catching a different lie:
- No wires exist yet. Interconnect RC is unmodelled or approximated by a wire load model. Placement will add real delay; routing will add more.
- The clock is ideal. Synthesis assumes the clock arrives everywhere simultaneously. CTS will build a real tree with real skew and real insertion delay, and it inserts hundreds of buffers that did not exist in this netlist.
- Only one corner was analysed. Meeting setup at typical says nothing about setup at slow or hold at fast.
- Hold was probably not checked at all. Hold violations are fixed after CTS, by inserting delay cells - they barely exist as a concept at synthesis.
- No DRVs were verified against real loads. A net's actual capacitance is not known until it is routed.
The sentence that lands: "Synthesis timing tells me the logic depth is roughly right. It is a feasibility check, not a result. The number I would actually quote is post-route STA at the slow corner."
Volume 02 recap
| Concept | The one thing to remember |
|---|---|
| Pass order | fsm/memory before techmap; dfflibmap before abc. |
| Generic netlist | The last library-independent form of your design. |
| ABC | AIG → restructure → cover with cells. Combinational only. |
| Complex cells | AOI/OAI collapse two levels into one. 20-30% of area and depth. |
| Drive strength | Upsizing moves the load problem upstream. Buffer trees beat one big cell. |
| DRVs | Fix max_transition first - it distorts every other number. |
stat |
Flop count is the honest metric. Any latch is a bug. |
| SDC | No clock, no target. Constrain 10-20% tight. |