The first lesson stated the rules; this one prices breaking them: how much area does the repair cost, and how does that bill grow as fanout widens and output load grows? Everything below is measured on the sized library across two fanouts, two output loads and two targets.
Two small designs carry the experiment: one driver feeding eight loads, and the same driver feeding thirty-two. Each is mapped four times: under the typical 0.05 pF output load and the heavy 0.2 pF load, each at a tight and a loose target. The script asserts the logic counts, nine and thirty-three NAND2-family cells in every run, and upsized cells on the tight heavy runs. Every buffered run saves its netlist, so each buffer count splits into outputs versus the shared net. A ninth run at a middle target feeds the break-it, and a sixteen-load half, mapped heavy and tight, feeds the cap comparison.
read_verilog <<EOV
module drv8(input a, input b, input [7:0] c, output [7:0] y);
wire n = ~(a & b);
genvar i;
generate for (i = 0; i < 8; i = i + 1) begin : g
assign y[i] = ~(n & c[i]);
end
endgenerate
endmodule
EOV
hierarchy -check -top drv8
synth -top drv8 -flatten
design -save gen8
design -reset
read_verilog <<EOV
module drv32(input a, input b, input [31:0] c, output [31:0] y);
wire n = ~(a & b);
genvar j;
generate for (j = 0; j < 32; j = j + 1) begin : g
assign y[j] = ~(n & c[j]);
end
endgenerate
endmodule
EOV
hierarchy -check -top drv32
synth -top drv32 -flatten
design -save gen32
design -load gen8
tee -o abc_8_typ.txt abc -liberty sized.lib -constr typical.constr -D 250
tee -o stat_8_typ.txt stat -liberty sized.lib
select -assert-count 9 t:NAND2 t:NAND2_X2 t:NAND2_X4
design -load gen8
tee -o abc_8_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 250
tee -o stat_8_heavy.txt stat -liberty sized.lib
write_json net_8_heavy.json
select -assert-count 9 t:NAND2 t:NAND2_X2 t:NAND2_X4
design -load gen32
tee -o abc_32_typ.txt abc -liberty sized.lib -constr typical.constr -D 250
tee -o stat_32_typ.txt stat -liberty sized.lib
write_json net_32_typ.json
select -assert-count 33 t:NAND2 t:NAND2_X2 t:NAND2_X4
design -load gen32
tee -o abc_32_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 250
tee -o stat_32_heavy.txt stat -liberty sized.lib
write_json net_32_heavy.json
select -assert-count 33 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-min 1 t:*_X2 t:*_X4 %u
design -load gen8
tee -o abc_8_loose_typ.txt abc -liberty sized.lib -constr typical.constr -D 1000
tee -o stat_8_loose_typ.txt stat -liberty sized.lib
select -assert-count 9 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-none t:*_X2 t:*_X4
design -load gen8
tee -o abc_8_loose_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 1000
tee -o stat_8_loose_heavy.txt stat -liberty sized.lib
write_json net_8_loose_heavy.json
select -assert-count 9 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-none t:*_X2 t:*_X4
design -load gen32
tee -o abc_32_loose_typ.txt abc -liberty sized.lib -constr typical.constr -D 1000
tee -o stat_32_loose_typ.txt stat -liberty sized.lib
write_json net_32_loose_typ.json
select -assert-count 33 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-none t:*_X2 t:*_X4
design -load gen32
tee -o abc_32_loose_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 1000
tee -o stat_32_loose_heavy.txt stat -liberty sized.lib
write_json net_32_loose_heavy.json
select -assert-count 33 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-none t:*_X2 t:*_X4
design -load gen32
tee -o abc_32_mid_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 400
tee -o stat_32_mid_heavy.txt stat -liberty sized.lib
write_json net_32_mid_heavy.json
select -assert-count 33 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-min 1 t:*_X2 t:*_X4 %u
design -reset
read_verilog <<EOV
module drv16(input a, input b, input [15:0] c, output [15:0] y);
wire n = ~(a & b);
genvar k;
generate for (k = 0; k < 16; k = k + 1) begin : g
assign y[k] = ~(n & c[k]);
end
endgenerate
endmodule
EOV
hierarchy -check -top drv16
synth -top drv16 -flatten
design -save gen16
design -load gen16
tee -o abc_16_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 250
tee -o stat_16_heavy.txt stat -liberty sized.lib
write_json net_16_heavy.json
select -assert-count 17 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-min 1 t:*_X2 t:*_X4 %u
log PASS: fanout sets the structure and the load sets the buffers and the target sets the sizesimport sys
def stages(fans, branch):
covered = 1
n = 0
while covered < fans:
covered = covered * branch
n = n + 1
return n
def tree_bufs(fans, branch):
total = 0
level = fans
while level > 1:
level = (level + branch - 1) // branch
total = total + level
return total
checks = [
(stages(8, 4), 2),
(tree_bufs(8, 4), 3),
(stages(32, 4), 3),
(tree_bufs(32, 4), 11),
]
for i in range(len(checks)):
got, want = checks[i]
if got != want:
print("FAIL: check " + str(i + 1) + " got " + str(got) + " want " + str(want))
sys.exit(1)
print("PASS: branch-4 repair needs 3 buffers for 8 sinks and 11 for 32")Numerically close but not by compounding: the cell count rises from 9 to 68 while the area per cell doubles through upsizing. Fanout sets the structure, load sets the repairs, and the target sets the sizes; the loose rows below prove the last part.
Fanout | Output load | Target | Cells | Area | Delay reached | Buffers (outputs + shared net) | Logic upsized |
|---|---|---|---|---|---|---|---|
8 loads | typical (0.05 pF) | tight | 9 | 18 | 201 ps | 0 (0+0) | 0 |
8 loads | heavy (0.2 pF) | tight | 17 | 62.8 | 249 ps | 8 (8+0) | 1 |
32 loads | typical (0.05 pF) | tight | 36 | 87.2 | 263 ps | 3 (0+3) | 3 |
32 loads | heavy (0.2 pF) | tight | 68 | 272.8 | 360 ps | 35 (32+3) | 11 |
8 loads | typical (0.05 pF) | loose | 9 | 18 | 201 ps | 0 (0+0) | 0 |
8 loads | heavy (0.2 pF) | loose | 17 | 34 | 342 ps | 8 (8+0) | 0 |
32 loads | typical (0.05 pF) | loose | 36 | 72 | 327 ps | 3 (0+3) | 0 |
32 loads | heavy (0.2 pF) | loose | 68 | 136 | 470 ps | 35 (32+3) | 0 |
Read the table down the load axis first, at the tight target. At 8 loads the heavy output load adds 8 (8+0) buffers where none stood, all on the outputs; at 32 loads it moves the shared-net repair from 3 to 3. The pattern matches the first lesson: each heavy output takes one buffer of its own, and only the remainder sits on the shared net. Fanout multiplies the structure; load multiplies the repairs. Then read across the target axis: every loose row keeps its tight row's cell count and buffer count exactly, while every upsized cell vanishes, and the 32-heavy area falls from 272.8 to 136 on sizes alone. The buffer count is load-driven and target-blind; the sizes are target-driven. The hand tree models full coverage at 11 buffers for 32 sinks; the recorded shared-net repair is 3 under both targets. Scorecard: both tight 32-load runs miss 250, typical by about 13 ps and heavy by about 110 ps; both tight 8-load runs land at about 201 and 249 ps, and every loose run meets its 1000 target.
The cap is structural, since ABC takes no numeric limit through this runner (abc -script, the only way to pass buffer -N, is rejected): the script maps a 16-load half under the same heavy, tight conditions, and two halves are what a cap of 16 allows:
Version | Buffers (outputs + shared net) | Area | Delay reached |
|---|---|---|---|
uncapped, 32 loads | 35 (32+3) | 272.8 | 360 ps |
one capped half, 16 loads | 17 (16+1) | 114.8 | 336 ps |
two halves (doubled) | 34 (32+2) | 229.6 | about 336 ps per half |
The cap trims the shared-net repair from 3 to 2 while the per-output buffers stand (32 against 32), landing at about 229.6 scaled. The doubled row is hand arithmetic on a recorded half; likewise, four capped copies of the recorded 8-load run remove the shared-net repair entirely.
The chart plots all eight recorded areas: fanout sets the group, output load the step, target the surcharge.
The ninth run maps the 32-heavy netlist at a middle target of 400 ps. It keeps every buffer, still split 32+3, with no logic upsized yet, hardens 30 of them to X4 and 3 to X2, and lands at area 235.6 at about 390 ps against the 400 asked, inside the target. The loose all-X1 row reaches about 470 ps and misses 400, so the middle run still buys sizes. Buffers stand while sizes arrive in steps: loosening sheds the X4s first, then the X2s.
Mostly structure: thirty-three NAND2-family logic cells plus the inserted buffers against nine logic cells and none, all asserted. The target-driven share is read down the target axis instead: the same 32-heavy netlist at the loose target sheds every upsized logic cell while the buffer count stands.
No. That repair fires only on a violation of its limit, and with no limit there is no violation. Timing optimisation could still buffer the net for delay, which is a separate decision with its own bill, measured the same way.
Every number and picture on this page comes from one Yosys 0.69 run of the script shown plus a stdlib-only Python check, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): ABC maps 8-load and 32-load drivers against sized.lib under typical.constr and heavy.constr at tight and loose targets, with stat and netlists saved per run so each buffer count splits into outputs versus the shared net, plus a ninth run of the 32-heavy netlist at a middle target for the break-it and a tenth run, a 16-load half, for the cap comparison; the program checks the branch-4 hand-tree sizes for both fanouts. No numeric fanout cap is passed to ABC: the cap is imposed structurally and the doubled row is hand arithmetic, both labelled so. The set_max_fanout tightening is a commercial-tool illustration and was not run. The libraries are fictional teaching files.