Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Logic Synthesis Glossary
  • Logic Synthesis Cheatsheet
  • Synthesis Foundations14
  • Logic Optimisation and Mapping20
    • Boolean Optimisation
    • Technology Mapping
    • Cell Libraries and Liberty
    • Structuring and Buffering
      • Fanout, Slew and Capacitance Rules
      • Delay of a Driver with Load
      • Buffer Trees
      • Sizing, Cloning and Restructuring
      • Fanout Constraints in Practice
  • Constraints and Timing15
  • Sequential, FSM and Memory19
  • Datapath Synthesis14
  • Area, Power and Test14
  • Verification, ECO and Handoff15
  • Flows and Debugging15
  • Interview and Judgment11
Loading lesson

←Structuring and Buffering
  1. Topics
  2. Logic Synthesis
  3. Logic Optimisation and Mapping
  4. Structuring and Buffering
  5. Buffer Trees
On this page

    Part 2 of 9, lesson 18 of 20Part 2 of 9/Logic Optimisation and Mapping/Lesson 18 of 20Intermediate

    Buffer Trees

    Ceiling-division tree sizing with the mapper's recorded repair.

    Floor division silently underbuilds every tree that is not an exact power of the branch, and clock nets mislead the whole exercise. Both mistakes ship overloaded buffers.

    After this lesson you can: size a buffer tree with ceiling division at every level, tell a synthesis-visible enable net from an ideal clock, and separate a hand-sized tree from the repair the mapper records.

    The question

    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.

    The shared net

    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.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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 target

    The 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:

    Diagram
    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_

    Stage count with ceiling division

    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.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    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 tree for 64 sinks

    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.

    Branch-4 buffer tree for 64 sinks: 1, 4 and 16 buffers across three stages, 21 buffers in total, every buffer driving at most four loads.

    What the mapper itself inserted

    Predict first The mapped census still shows the shared structure. Will it also show the full-coverage hand tree?

    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.

    The branch-width optimum

    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.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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")
    Check yourself Why is branch 4 optimal only for this stage formula?

    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.

    Break it: heavy load, tight target

    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.

    Common mistakes

    Buffers fix slew but add their own stage delays. A tree that cleans every edge can still miss timing, so recheck arrival after insertion. Never demonstrate fanout repair on a clock: synthesis treats clocks as ideal, and the physical tree is built later by clock-tree synthesis.

    Q: 100 sinks, branch 4. How many buffers?

    Weak answer: 32, from dividing down: 25, 6, 1. Strong answer: 35, with ceiling division at every level: 25, 7, 2, 1, because the fractional groups still need drivers; the floor count leaves the top buffer driving six children against a branch of four. The recorded repair is the contrast: 64 mapped DFF cells sharing one enable net draw only 7 buffers from ABC's own pass, a surgical repair where its cost function flagged, against 21 for the full-coverage hand tree. Follow-up: price branch 2. Seven stages and 102 buffers, checked by the same program, nearly triple the area for a slower tree.

    Check yourself 100 sinks need branch-4 buffers. Give the per-stage counts and explain why the last two stages are 2 and 1, not 1 and 1.

    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.

    Check yourself The mapped census shows the recorded repair next to the hand-sized tree. Does the recording contradict the arithmetic?

    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.

    • Round buffer counts up at every stage: 100 sinks at branch 4 need 25, 7, 2, 1.

    • Synthesis treats clocks as ideal; repair trees on enables and resets.

    • ABC's buffer step repairs only the nets its cost function flags, not every loaded net.

    Rule of thumb: never let a buffer drive more children than the branch allows.

    How this was checked

    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.

    • Interview: run a mock interview on this topic

    Related topics

    • Sizing, cloning and restructuring

    • Technology mapping

    Previous

    34 / 139

    Next: Sizing, Cloning and Restructuring

    Up next

    Up nextSizing, Cloning and Restructuring

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Fanout, Slew and Capacitance Rules
    • Fanout Constraints in Practice