Arrival time grows with the number of gate stages on a path: every stage adds its own delay, so a shorter path arrives earlier for the same function. An 8-input AND makes the point concrete. Written as a chain of two-input AND gates, the last input waits through seven stages; written as a balanced tree of pairs, no input waits through more than three. The two scripts below synthesise each spelling twice: first with a light flow that preserves the written structure, so the recorded netlist shows the shape as written, then with the full flow and ABC so the recorded report shows what the tool makes of each shape. The light flow is hierarchy, proc, flatten, techmap and clean: no optimisation pass, so nothing rebalances the chain before it is written out.
read_verilog <<EOV
module chain8(input [7:0] x, output y);
wire c0 = x[0] & x[1];
wire c1 = c0 & x[2];
wire c2 = c1 & x[3];
wire c3 = c2 & x[4];
wire c4 = c3 & x[5];
wire c5 = c4 & x[6];
assign y = c5 & x[7];
endmodule
EOV
hierarchy -top chain8
proc
flatten
techmap
clean
write_json chain_raw.json
design -reset
read_verilog <<EOV
module chain8(input [7:0] x, output y);
wire c0 = x[0] & x[1];
wire c1 = c0 & x[2];
wire c2 = c1 & x[3];
wire c3 = c2 & x[4];
wire c4 = c3 & x[5];
wire c5 = c4 & x[6];
assign y = c5 & x[7];
endmodule
EOV
synth -top chain8 -flatten
design -save chain
tee -o chain_loose.txt abc -liberty sized.lib -constr typical.constr -D 100000
tee -o chain_loose_stat.txt stat -liberty sized.lib
write_json chain_net.json
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
check
design -load chain
tee -o chain_tight.txt abc -liberty sized.lib -constr typical.constr -D 250
tee -o chain_tight_stat.txt stat -liberty sized.lib
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
select -assert-min 1 t:*_X2 t:*_X4
check
log PASS: a chain maps the way a tree mapsread_verilog <<EOV
module tree8(input [7:0] x, output y);
wire a0 = x[0] & x[1];
wire a1 = x[2] & x[3];
wire a2 = x[4] & x[5];
wire a3 = x[6] & x[7];
wire b0 = a0 & a1;
wire b1 = a2 & a3;
assign y = b0 & b1;
endmodule
EOV
hierarchy -top tree8
proc
flatten
techmap
clean
write_json tree_raw.json
design -reset
read_verilog <<EOV
module tree8(input [7:0] x, output y);
wire a0 = x[0] & x[1];
wire a1 = x[2] & x[3];
wire a2 = x[4] & x[5];
wire a3 = x[6] & x[7];
wire b0 = a0 & a1;
wire b1 = a2 & a3;
assign y = b0 & b1;
endmodule
EOV
synth -top tree8 -flatten
tee -o tree_loose.txt abc -liberty sized.lib -constr typical.constr -D 100000
tee -o tree_loose_stat.txt stat -liberty sized.lib
write_json tree_net.json
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
check
log PASS: a tree maps the way a chain mapsThe assertions prove the generic gates are gone from the mapped netlists. The raw netlists are unoptimised, so their depth is the depth as written, checked by the program further down.
No: ABC rebalances the chain into a balanced cover on its own, so the RTL spelling stops mattering once the full flow runs. The mapping table below shows both spellings landing on identical reports.
Spelling (recorded raw netlist, hand-derived depth) | Generic cells | Depth in stages |
|---|---|---|
Chain of 7 AND gates | 7 | 7 |
Balanced tree of 7 AND gates | 7 | 3 |
The mapped reports erase the difference completely. The chain maps to 5 cells at area 13 and 382 ps; the tree maps to 5 cells at area 13 and 382 ps. Same function, same library, same target, same cover: the mapper's restructuring already did the balancing the tree spelling did by hand. That is timing-driven synthesis spending restructuring on depth before it spends any area on bigger cells.
Mapping (recorded ABC report) | Cells | Area | Delay reached |
|---|---|---|---|
Chain, loose target | 5 | 13 | 382 ps |
Tree, loose target | 5 | 13 | 382 ps |
Chain, tight -D 250 target | 5 | 27.4 | 230 ps |
The tight row shows the second half. At -D 250 the chain's cover cannot get shallower, so the mapper buys speed with drive strength instead: area rises from 13 to 27.4 while delay falls from 382 ps to 230 ps. Restructure first, size second: depth is cheaper than drive, and the tool tries them in that order.
The program below checks the depth rules against the two depths parsed from the recorded raw netlists (7 and 3): a chain of n inputs needs n minus 1 stages, a balanced tree needs the ceiling of log base 2 of n. Its checks fail on any other pair, so the raw table above cannot drift from the recordings without the run going red.
import sys
# Depth rules for an n-input AND of two-input gates, checked against the
# depths parsed from the recorded raw netlists: 7 and 3.
def chain_depth(n):
return n - 1
def tree_depth(n):
depth = 0
span = 1
while span < n:
span *= 2
depth += 1
return depth
if chain_depth(8) != 7:
print("FAIL: chain depth " + str(chain_depth(8)) + " want 7")
sys.exit(1)
if tree_depth(8) != 3:
print("FAIL: tree depth " + str(tree_depth(8)) + " want 3")
sys.exit(1)
print("PASS: an 8-input AND is 7 stages as a chain and 3 as a balanced tree")The third script maps the shared cone three ways, loose, tight, and under a heavy load, then maps the kept-duplicate design once. Every assertion in it proves the generic gates are gone, the tight runs use upsized cells, and the heavy run inserts a buffer.
read_verilog <<EOV
module sharedrv(input a, b, c, d, e, f, output y1, y2);
wire t = (a & b) | (c & d);
assign y1 = t ^ e;
assign y2 = t ^ f;
endmodule
EOV
synth -top sharedrv -flatten
write_json shr_gen.json
design -save shr
tee -o shr_loose.txt abc -liberty sized.lib -constr typical.constr -D 100000
tee -o shr_loose_stat.txt stat -liberty sized.lib
write_json shr_net.json
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
check
design -load shr
tee -o shr_tight.txt abc -liberty sized.lib -constr typical.constr -D 300
tee -o shr_tight_stat.txt stat -liberty sized.lib
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
select -assert-min 1 t:*_X2 t:*_X4
check
design -load shr
tee -o shr_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 100000
tee -o shr_heavy_stat.txt stat -liberty sized.lib
write_json shr_heavy_net.json
select -assert-min 1 t:BUF*
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
check
design -reset
read_verilog <<EOV
module dupkeep(input a, b, c, d, e, f, output y1, y2);
(* keep *) wire t1 = (a & b) | (c & d);
(* keep *) wire t2 = (a & b) | (c & d);
assign y1 = t1 ^ e;
assign y2 = t2 ^ f;
endmodule
EOV
synth -top dupkeep -flatten
write_json dup_gen.json
tee -o dup_loose.txt abc -liberty sized.lib -constr typical.constr -D 100000
tee -o dup_loose_stat.txt stat -liberty sized.lib
write_json dup_net.json
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
check
log PASS: a kept duplicate still maps to one shared coneA shared cone serves several paths at once, so its shape compromises between all of them. Duplication gives the critical path its own private copy shaped for speed alone. One design in that script shares a single AND-OR cone between two XOR outputs, while the changed variant computes the same cone twice behind keep attributes. The keep attribute saves the wire, but boolean optimisation still proves the two cones identical and shares almost everything, so the duplicate costs a single extra cell before mapping.
Mapping then erases even that. Both designs map to 5 cells at area 16: the shared run at 366 ps, the duplicated run at 382 ps. ABC's default script runs fraig and strash, which merge functionally identical nodes no matter what the RTL said, so a naive RTL copy never survives to the netlist; the tool un-duplicates the keep-attribute copy here. A private copy has to survive the tool: separate it with a register, or let the mapper replicate during covering.
Shared cone (recorded) | Generic cells | Mapped cells | Mapped area | Mapped delay |
|---|---|---|---|---|
One cone, two outputs | 5 | 5 | 16 | 366 ps |
One cone, tight -D 300 target | 5 | 5 | 32 | 295 ps |
Duplicated cone, keep attributes | 6 | 5 | 16 | 382 ps |
One cone, heavy 0.2 pF load | 5 | 7 | 20 | 506 ps |
The heavy-load row shows the other answer to a struggling fanout: buffering. Against a 0.2 pF load the same shared design maps to 7 cells at area 20, and the two extra cells are buffers the lighter runs never needed. Yet the delay still rises from 366 ps to 506 ps. Buffering and sizing answer load and drive; restructuring answers depth: buffering repairs the drive the heavy capacitance degrades, but it cannot shorten the logic the signal passes through. Sizing costs 16 area units for speed here, as the new tight row shows; buffering costs 4 and still arrives later.
The duplicated design is the break-it: the same function, library and loose target, with the shared cone written twice. Everything about the setup favours the duplicate, and the recorded result still refuses it: 6 generic cells collapse to the same 5 mapped cells at the same area 16 as the shared design. The only trace of the copy is a 16 ps wobble in the reported delay, noise from a different starting point, not a faster structure. An RTL copy that the tool can prove identical is area you wrote and the tool deleted.
Into the mapper's restructuring: ABC rebalances the chain into a balanced cover on its own, so both spellings land on the same cells, area and delay. The RTL depth only shows in the unoptimised raw netlists.
Yes, at the generic level: the recorded generic counts read 5 against 6. Mapping merged it back, because fraig proves the copies identical. The copy cost one cell and bought no delay.
Every number and picture on this page comes from Yosys 0.70 runs of the three scripts shown, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): unoptimised chain and tree netlists from a light hierarchy-proc-flatten-techmap-clean flow, ABC mappings of both against sized.lib loose and the chain tight, and shared, kept-duplicate and heavy-load mappings of a shared-cone design. Depths are hand-derived from the recorded raw netlists; the depth program checks the same two numbers from the depth rules. The library is fictional, so absolute figures are teaching units.