Real flows let the mapper spend area three ways: sizing a cell up, cloning a driver so each copy feeds fewer sinks, and restructuring logic to cut depth. This lesson records the first across target and load, then prices the other two with hand arithmetic that names its sources; the break-it is a recorded chain/tree pair.
Sizing swaps a cell for a stronger sibling with the same function: plain, X2 and X4 in the teaching library, each step faster and larger. The script below maps one decoder five times: loosely and tightly at the typical output load, loosely and tightly at the heavy load, and once more at the tight target against the single-size library, which has no X2 or X4 to buy. The loose runs must use only X1 cells and the tight sized runs at least one bigger one, all asserted.
read_verilog <<EOV
module decode (input [11:0] x, output y, output z);
assign y = ((x[0] & x[1]) | (x[2] & x[3] & x[4]) | (x[5] ^ x[6]) | (x[7] & ~x[8])) & (x[9] | x[10] | ~x[11]);
assign z = ^x[7:0] ^ (x[8] & x[9]);
endmodule
EOV
synth -top decode -flatten
design -save generic
tee -o abc_loose.txt abc -liberty sized.lib -constr typical.constr -D 1000
tee -o stat_loose.txt stat -liberty sized.lib
write_json net_loose.json
select -assert-none t:*_X2 t:*_X4
design -load generic
tee -o abc_loose_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 1000
tee -o stat_loose_heavy.txt stat -liberty sized.lib
select -assert-none t:*_X2 t:*_X4
design -load generic
tee -o abc_tight.txt abc -liberty sized.lib -constr typical.constr -D 600
tee -o stat_tight.txt stat -liberty sized.lib
write_json net_tight.json
select -assert-min 1 t:*_X2 t:*_X4 %u
design -load generic
tee -o abc_tight_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 600
tee -o stat_tight_heavy.txt stat -liberty sized.lib
select -assert-min 1 t:*_X2 t:*_X4 %u
design -load generic
tee -o abc_tight_1size.txt abc -liberty course.lib -constr typical.constr -D 600
tee -o stat_tight_1size.txt stat -liberty course.lib
select -assert-none t:*_X2 t:*_X4
design -reset
read_verilog <<EOV
module chain8(input [7:0] x, output y);
wire s1 = x[0] & x[1];
wire s2 = s1 & x[2];
wire s3 = s2 & x[3];
wire s4 = s3 & x[4];
wire s5 = s4 & x[5];
wire s6 = s5 & x[6];
assign y = s6 & x[7];
endmodule
EOV
synth -top chain8 -flatten
design -save chain
design -reset
read_verilog <<EOV
module tree8(input [7:0] x, output y);
wire t0 = x[0] & x[1];
wire t1 = x[2] & x[3];
wire t2 = x[4] & x[5];
wire t3 = x[6] & x[7];
wire u0 = t0 & t1;
wire u1 = t2 & t3;
assign y = u0 & u1;
endmodule
EOV
synth -top tree8 -flatten
design -save tree
design -load chain
tee -o abc_chain.txt abc -liberty sized.lib -constr typical.constr -D 1000
tee -o stat_chain.txt stat -liberty sized.lib
select -assert-count 5 t:AND2 t:NAND2 t:NAND3 t:NOR3
design -load tree
tee -o abc_tree.txt abc -liberty sized.lib -constr typical.constr -D 1000
tee -o stat_tree.txt stat -liberty sized.lib
select -assert-count 5 t:AND2 t:NAND2 t:NAND3 t:NOR3
log PASS: the target buys sizes and the load buys buffers and one size buys nothingIt keeps the structure and buys drive: the same 18 cells with 2 upsized logic cells at the tight target, unchanged when the heavy load is added on top.
Run | Cells | Area | Delay reached | Buffers | Logic upsized |
|---|---|---|---|---|---|
loose, typical | 18 | 65 | 750 ps | 0 | 0 |
loose, heavy | 20 | 69 | 890 ps | 2 | 0 |
tight, typical | 18 | 81 | 594 ps | 0 | 2 |
tight, heavy | 20 | 88.2 | 678 ps | 2 | 2 |
tight, one size | 18 | 65 | 750 ps | 0 | 0 (none exist) |
Read the table along its two axes against the 600 ps the tight runs asked for. Down the target axis at the typical load, the count holds at 18 cells while 2 XNOR2 cells harden to X4: same structure, stronger slow path, landing at about 594 ps against the 600 asked, inside by about 6 ps. Down the load axis at the loose target, the count moves from 18 to 20 with every logic cell still size 1: the heavier output load inserts 2 buffers with no target pressure. The tight heavy run stacks both repairs yet still misses, about 78 ps short; the single-size tight run misses further, about 150 ps short: with nothing stronger to buy, the target buys nothing.
Recorded size histogram, every cell accounted for | Tight typical | Tight heavy |
|---|---|---|
NAND2 at X1 (never moves) | 2 | 2 |
XNOR2 at X1 | 3 | 3 |
XNOR2 at X4 (the slow path) | 2 | 2 |
BUF at X1 | 0 | 1 |
BUF at X4 (upsized with the load) | 0 | 1 |
all other cells (stay X1) | 11 | 11 |
The histogram accounts for every cell: both tight sized runs upsize XNOR2 only, with 11 other cells staying X1 in the typical run and 11 other cells in the heavy one, and no buffer appears until the load grows. It also names the tight heavy run's extra area over the loose heavy run: 19.2 units, of which 16 come from the two XNOR2 cells growing from 5 to 13 each and 3.2 from the one BUF hardening from 2 to 5.2, all four digits copied exactly from the sized library.
Both netlists come from the saved write_json files. The loose netlist is all size 1; the tight one keeps the structure with 2 XNOR2 cells upsized to X4, drawn in orange.
Cloning duplicates the driver so each copy feeds a subset of the sinks: less load per copy and sharper edges, at the price of a second driver. The other side sits upstream: the feeding net now drives two input pins. The arithmetic below uses the library's own digits: an X1 inverter input is 0.01 pF and an X1 inverter cell is area 1, both copied exactly, with the inverter rise row giving 0.05 ns at 0.01 pF and 0.07 ns at 0.1 pF.
hand arithmetic from the copied inverter row (0.05 ns at 0.01 pF, 0.07 at 0.1 pF):
slope k = (0.07 - 0.05) / 0.09 = 0.2222 ns per pF
before: driver sees wire 0.02 pF plus 12 loads of 0.01 pF = 0.14 pF (extrapolated: past the 0.1 pF table edge)
D = 0.05 + 0.2222 x 0.13 = 0.0789 ns, about 79 ps (extrapolated)
after 2-way cloning (6 sinks per copy):
each copy sees 0.02 + 6 x 0.01 = 0.08 pF
D = 0.05 + 0.2222 x 0.07 = 0.0656 ns, about 66 ps
saving = 79 - 66 = 13 ps for twice the driver area (1 + 1 = 2 units)
upstream bill: the feeding net gains one more 0.01 pF input pin, 0.01 to 0.02 pFThirteen picoseconds saved against a doubled driver and a heavier upstream net. When the upstream net already runs near its own slew limit, the clone files another violation while fixing one, so a buffer tree or stronger upstream driver belongs in the same repair.
Balanced trees cut logic depth without touching load: an 8-input AND needs seven two-input gates however arranged, but a chain of seven has depth 7 against 3 for the balanced tree of 4 plus 2 plus 1. Restructuring pairs with buffering rather than replacing it: one cuts stages of logic, the other cuts load per stage.
8-input AND from 2-input AND gates: 8 - 1 = 7 gates either way
chain: 7 gates in series -> depth 7
balanced tree: 4 + 2 + 1 gates per level -> depth 3
same count, depth 3 against 7Restructuring has had no recorded run so far; this break-it adds the pair. The script maps two eight-input ANDs: one written as a chain of seven two-input gates, one as a balanced tree of 4 plus 2 plus 1. The abc -D script's rewriting canonicalizes both RTLs to the same cell mix at the same area and arrival, before any size or buffer decision:
RTL | Cells | Cell mix | Area | Delay reached |
|---|---|---|---|---|
chain of seven | 5 | 1 AND2, 2 NAND2, 1 NAND3, 1 NOR3 | 13 | 382 ps |
balanced tree | 5 | 1 AND2, 2 NAND2, 1 NAND3, 1 NOR3 | 13 | 382 ps |
Restructuring is the one repair the mapper does unasked; sizes and buffers wait for pressure. The hand depth arithmetic, 7 against 3, prices what the RTL author controls, not what the mapper keeps. The fifth row above still shows the other side: the same tight target against the single-size library matches the loose typical run exactly, since a constraint steers the tool only among choices the library provides.
About 79 ps before and about 66 ps after, saving 13 ps for twice the driver area. The feeding net pays one more 0.01 pF input pin.
Same logic, stronger cells on the slow path: the arrangement is unchanged and only the drive strengths moved. Restructuring would change the gate arrangement and depth at a similar count.
Every number and picture on this page comes from one Yosys 0.69 run of the script shown, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): ABC maps one decoder against sized.lib at loose and tight delay targets under typical.constr and heavy.constr, plus one tight run against the single-size library and a chain-RTL versus tree-RTL pair, with stat and netlists saved. The cloning arithmetic is hand-derived from pin digits copied exactly from sized.lib and labelled so, as are the 7-against-3 depth counts. The libraries are fictional teaching files.