One signal must reach dozens of sinks, and no single driver can carry them all. The fix is a tree of buffers in stages, each stage splitting the load into small groups. Two traps wait on the way there: sizing the tree with floor division, which underbuilds any sink count off an exact power of the branch, and demonstrating fanout on a clock net, which synthesis treats as ideal. This lesson uses ceiling division throughout, on a high-fanout enable net that synthesis genuinely sees, and then checks what the mapper itself inserted.
Sixty-four flops each sample their data bit gated by one shared enable. That enable fans out to sixty-four AND gates, one per flop, and every bit of it is ordinary data as far as synthesis is concerned: no ideal-net exemption applies to it. The script below builds that net, saves the generic design and its census, then maps it with the mapper's own output saved. The assertions pin the flop count twice, generic and mapped, plus the mapped AND2 and buffer counts; the saved netlist pins how the buffers split the loads.
read_verilog <<EOV
module fanen(input clk, input en, input [63:0] d, output reg [63:0] q);
genvar i;
generate for (i = 0; i < 64; i = i + 1) begin : g
always @(posedge clk) q[i] <= d[i] & en;
end
endgenerate
endmodule
EOV
hierarchy -check -top fanen
synth -top fanen
design -save generic
tee -o stat_generic.txt stat
select -assert-count 64 t:$_DFF_P_
read_liberty -lib sized.lib
dfflegalize -cell $_DFF_P_ 01 -cell $_DFF_PN0_ 01
dfflibmap -liberty sized.lib
tee -o abc_mapped.txt abc -liberty sized.lib -constr typical.constr -D 1000
tee -o stat_mapped.txt stat -liberty sized.lib
write_json net_mapped.json
select -assert-count 64 t:DFF
select -assert-count 64 t:AND2
select -assert-count 7 t:BUF
design -load generic
read_liberty -lib sized.lib
dfflegalize -cell $_DFF_P_ 01 -cell $_DFF_PN0_ 01
dfflibmap -liberty sized.lib
tee -o abc_tight_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 250
tee -o stat_tight_heavy.txt stat -liberty sized.lib
write_json net_tight_heavy.json
select -assert-count 64 t:DFF
select -assert-count 64 t:AND2
select -assert-min 1 t:BUF t:BUF_X2 t:BUF_X4 %u
log PASS: 64 flops share one enable net and the mapper repairs it at the loose targetThe assertions pin sixty-four generic flops, then sixty-four mapped DFF cells with sixty-four AND2 cells and the inserted buffers. The census on the generic netlist reads:
recorded generic census
4. Printing statistics.
=== fanen ===
+----------Local Count, excluding submodules.
|
68 wires
194 wire bits
4 public wires
130 public wire bits
4 ports
130 port bits
128 cells
64 $_AND_
64 $_DFF_P_A tree of branch b covers b to the s sinks in s stages: the stage count is the first branch power that reaches the sink count. Sixty-four sinks at branch 4 need three stages; one hundred need four. Within each stage the buffer count divides the remaining sinks by the branch and rounds up. Flooring 100 by 4 gives 25, then 6, then 1, totalling 32, and the top buffer quietly drives six children against a branch of four. Ceiling gives 25, then 7, then 2, then 1, totalling 35, and every buffer drives at most four loads.
import 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
def floor_bufs(fans, branch):
total = 0
level = fans
while level > 1:
level = level // branch
total = total + level
return total
checks = [
(stages(64, 4), 3),
(tree_bufs(64, 4), 21),
(stages(100, 4), 4),
(tree_bufs(100, 4), 35),
((100 + 4 - 1) // 4, 25),
(floor_bufs(100, 4), 32),
(stages(100, 2), 7),
(tree_bufs(100, 2), 102),
]
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: ceiling tree math gives 21 buffers for 64 sinks and 35 for 100; floor gives 32; branch 2 gives 102")Sinks (branch 4) | Stages | Buffers per stage | Total |
|---|---|---|---|
64 | 3 | 16, 4, 1 | 21 |
100 | 4 | 25, 7, 2, 1 | 35 |
The schematic draws the branch-4 tree the arithmetic sizes: one driver, one buffer, then four, then sixteen, reaching all sixty-four sinks in three stages. Each wire fans out to at most four children.
No. The recorded repair is a fraction of the hand tree: ABC's buffer step repairs only the nets its own cost function flags instead of building full coverage. The hand tree is model arithmetic, checked by the program; the repair is recorded.
View | Buffers | Source |
|---|---|---|
generic census (recorded) | 0: 64 $_AND_ plus 64 $_DFF_P_ | stat excerpt above |
hand branch-4 tree (model) | 21 | checked program, schematic above |
ABC mapped (recorded) | 7 | stat census and saved ABC report |
Read the table as three different claims. The census proves the problem: sixty-four AND gates plus sixty-four flops. The hand tree sizes one full-coverage fix for it. ABC's repair is surgical: 64 AND2 plus 64 DFF cells survive mapping, reaching about 406 ps. The saved netlist shows how the 7 buffers split the 64 loads: one root plus 6 leaves, every buffer driving 10 loads, for 60 through the leaves plus 4 direct from the root. Every buffer here drives at most 10 loads, matching the step's -N default of 10 in this build. The hand tree is a model, not a prediction of ABC: the mapper pays only where its cost function fires.
Wider branches need fewer stages but slower stages. Model each stage as costing a base of 2 plus one per child it drives. That formula is a modelling choice, not a library number: a different slope or base moves the minimum. Under it, branch 2, 4 and 8 price at 24, 18 and 20 model units, and branch 4 wins; the optimum belongs to the formula and must be recomputed per library.
import sys
FANS = 64
def stages(fans, branch):
covered = 1
n = 0
while covered < fans:
covered = covered * branch
n = n + 1
return n
# Pure stage-delay model, not a library number: each stage costs a base of 2
# plus one per child it drives. The optimum below belongs to this formula.
def tree_delay(branch):
return stages(FANS, branch) * (2 + branch)
totals = [(2, tree_delay(2)), (4, tree_delay(4)), (8, tree_delay(8))]
want = [(2, 24), (4, 18), (8, 20)]
for i in range(len(totals)):
got, want_pair = totals[i], want[i]
if got != want_pair:
print("FAIL: branch " + str(got[0]) + " got " + str(got[1]) + " want " + str(want_pair[1]))
sys.exit(1)
best = min(totals, key=lambda p: p[1])
if best[0] != 4:
print("FAIL: best branch got " + str(best[0]) + " want 4")
sys.exit(1)
print("PASS: branch 4 wins at 18 model units against 24 and 20")The totals, 24, 18 and 20, come from multiplying the stage count by 2 plus the branch. A different library slope or base changes every product, so the minimum moves with the library and must be recomputed, not remembered.
The script maps the same enable net again under the heavy output load and the tight target of 250. The repair grows from the loose run's tree to 71 buffers: 7 on the enable side against 64 sitting singly between an AND2 and its flop. The enable side keeps its shape, one root plus 6 leaves, now all in X2; the new buffers are slew and gain repair on loaded edges, not fanout splitting. Area climbs from 206 to about 534.4 for an arrival of about 448 ps against the 250 asked, still missing it: which threshold fired is not isolated by these runs, but a fanout count cannot explain buffers whose fanout is one.
Ceiling division at every level. Seven groups of four need two buffers since 7 over 4 is 1.75, rounded up; those two need one buffer above them. Flooring either step strands loads without a driver.
No. The hand tree is the full-coverage model, checked by the program and drawn in the schematic; the recorded repair is what ABC's own cost function paid for. A census only shows passes that ran, and the hand tree never did.
Every number and picture on this page comes from one Yosys 0.69 run of the script shown plus stdlib-only Python checks, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): 64 enable-gated flops are synthesised, asserted generic and mapped, with the generic census, the mapped ABC report and both netlists saved; the programs check the ceiling counts and the branch-width optimum of a stated stage-delay model. A second recorded run maps the same netlist under the heavy load at the tight target for the break-it. The tree schematic is drawn by the lesson code, not by any tool. The libraries are fictional teaching files.