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. Fanout Constraints in Practice
On this page

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

    Fanout Constraints in Practice

    The measured cost of repair: area against fanout and load.

    A fanout limit is a purchase order for area. This page is the price list: what wider fanout and heavier load each add, measured, so the next limit you set is budgeted.

    After this lesson you can: split a repair bill into its structural part and its sizing surcharge and predict which axis a limit change moves.

    The question

    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.

    What was measured

    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.

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    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 sizes
    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
    
    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")

    Two fanouts times two loads times two targets is eight mapper runs in one script, each saved and reloaded from its generic netlist, plus a ninth run at a middle target for the break-it and a tenth, the capped half, for the fanout-cap comparison. The program checks the hand tree sizes for exactly these two fanouts at branch 4: 3 buffers for 8 sinks, 11 for 32.

    What the eight runs printed

    Predict first Four times the sinks and four times the output load: will the heavy 32-load area be roughly sixteen times the typical 8-load area?

    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.

    What a fanout cap buys

    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.

    Area by load and target

    The chart plots all eight recorded areas: fanout sets the group, output load the step, target the surcharge.

    Recorded mapped area for the two fanouts under both output loads at both targets, tight rows then loose rows. Compare the typical bar against the heavy bar inside each fanout pair, and each tight row against its loose twin.

    Break it: move the target to the middle

    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.

    Common mistakes

    Tighter limits can cost more somewhere: a max fanout of 4 on every net would grow trees everywhere, so set limits from the library's drive tables and recheck area after every repair, not just timing. The 8-typical pair shows the other side: X1 cells already meet both targets, so the tighter one costs nothing extra.

    Q: the design's fanout grew from 8 to 32 loads and the repair bill exploded. Where did the area go?

    Weak answer: into buffers. Strong answer: into two places with different signatures. The structural part follows the fanout: thirty-three NAND2-family cells against nine, asserted in every run, plus the load-driven buffers: a shared-net repair of 3 at 32-heavy against 0 at 8-heavy. The target-driven part is the sizes on the same netlist: the tight 32-heavy run carries upsized cells the loose twin sheds while the buffer count stands. Quote both numbers and say which axis moved. Follow-up: the bill did not move at all. That is the 8-typical pair, identical at 18 under both targets: X1 cells already met the loose and the tight line, so there was nothing to buy.

    Check yourself The tight 32-heavy run against the tight 8-typical run: how much of that gap is structure, and how much is target-driven sizing?

    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.

    Check yourself No max fanout limit is set and a net drives 200 sinks. Does the max fanout repair fire?

    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.

    • Fanout sets the logic; load sets the buffer count; the target sets the sizes.

    • Loosen the target and the buffer counts stand while every X2 and X4 vanishes.

    • A repair that fires on no violation is timing work, not a rule fix.

    Rule of thumb: budget fanout repairs per axis: cells for the fanout, buffers for the load, sizes for the target.

    How this was checked

    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.

    • Interview: run a mock interview on this topic

    Related topics

    • Fanout, slew and capacitance rules

    • SDC for synthesis

    • Timing driven synthesis

    Previous

    36 / 139

    Next: Constraints and Timing

    Up next

    Up nextConstraints and Timing

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Fanout, Slew and Capacitance Rules
    • Delay of a Driver with Load
    • Buffer Trees