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
      • Constant Propagation
      • Redundancy Removal
      • Factoring, Collapsing and Sharing
      • AIGs, Depth and Area
      • Don't-Cares
    • Technology Mapping
    • Cell Libraries and Liberty
    • Structuring and Buffering
  • 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

←Boolean Optimisation
  1. Topics
  2. Logic Synthesis
  3. Logic Optimisation and Mapping
  4. Boolean Optimisation
  5. Factoring, Collapsing and Sharing
On this page

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

    Factoring, Collapsing and Sharing

    Literal counts, sub-expression reuse, and a runnable equivalence check.

    Hand arithmetic prices one cover while the tool prices thousands. Without both numbers side by side, engineers either distrust the tool or worship a hand count.

    After this lesson you can: price a factored form by hand, read the area ABC reaches, and explain the gap from the recorded cell histogram.

    Factoring pulls, collapsing pushes

    Factoring pulls a common literal out of several product terms, turning a flat sum into a nested form with fewer literals. Collapsing does the reverse: it substitutes one expression into another, flattening the logic so that new common factors appear. The two moves are a pair because collapsing alone usually makes things worse; it pays only when a second factoring pass harvests what the flattening exposed.

    The running example is the 3-input majority function, which outputs 1 whenever at least two inputs are 1. Its sum of products lists all three input pairs. The factored form pulls a out of the first two pairs and shares the work that remains. Both forms compute the same function on all eight input rows, and the program near the end of this lesson checks every one of them.

    Form

    Expression

    Literals

    Sum of products

    ab + ac + bc

    6

    Factored

    a(b + c) + bc

    5

    What ABC reaches

    Hand counts price one cover you chose; ABC prices every cover the library allows. The script below maps the majority function to the full teaching library and records the area ABC actually reaches, with the cell histogram naming the cover it picked. Areas are library units of the fictional teaching library, never silicon figures.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    read_verilog <<EOV
    module maj3(input wire a, input wire b, input wire c, output wire y);
    assign y = (a & b) | (a & c) | (b & c);
    endmodule
    EOV
    hierarchy -check -top maj3
    proc
    opt
    techmap
    tee -o abc_maj.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_maj.txt stat -liberty course.lib
    write_json net_maj.json
    select -assert-none t:$and t:$or t:$not
    check
    log PASS: majority maps to library cells with no generic gates left
    Predict first The hand count below prices the factored form at 12 area units. Will ABC match it, come in lower, or higher?

    ABC reaches 7 area units. The hand count assumes an AND2 and OR2 cover, while ABC chooses from the whole library, including cheaper inverting cells and complex cells the hand count never considers. The recorded histogram reads 2 x NAND2, 1 x OAI21, which is the cover that won.

    Diagram
    hand count, AND2 and OR2 at 3 units each from the course.lib cell table
    original ab + ac + bc: 3 AND2 + 2 OR2 (3-input OR from two OR2)
    area = 3 x 3 + 2 x 3 = 9 + 6 = 15 area units
    factored a(b + c) + bc: 2 AND2 + 2 OR2
    area = 2 x 3 + 2 x 3 = 6 + 6 = 12 area units
    saving = 15 - 12 = 3 area units = 3 / 15 = 20%

    Set the two hand counts side by side: 15 units for the flat form against 12 for the factored form. The hand saving of 3 units is real but partial, because it compares two covers you drew. When the recorded area beats the hand count, cheaper inverting cells (NAND2 and NOR2 at 2 units against AND2 and OR2 at 3) and complex cells did the extra work; when it trails, the structure deserves a second look, not blind trust.

    The majority function as ABC covered it: 2 x NAND2, 1 x OAI21 at area 7.

    Sharing

    Sharing implements one repeated sub-expression once and reuses its output at every user. The second design computes two outputs from one shared OR: y1 = a & (b | c) and y2 = d & (b | c). The RTL writes the (b | c) term twice, so after elaboration the recorded netlist holds two identical OR cells. The opt stage merges them, and the assertions pin the shared result: one OR cell feeding two AND cells.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    read_verilog <<EOV
    module share2(input wire a, input wire b, input wire c, input wire d, output wire y1, output wire y2);
    assign y1 = a & (b | c);
    assign y2 = d & (b | c);
    endmodule
    EOV
    hierarchy -check -top share2
    proc
    opt
    check
    write_json net_share_opt.json
    select -assert-count 1 t:$or
    select -assert-count 2 t:$and
    techmap
    tee -o abc_share.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_share.txt stat -liberty course.lib
    write_json net_share.json
    select -assert-none t:$and t:$or t:$not
    check
    log PASS: one shared OR feeds both ANDs through mapping
    Diagram
    hand count for the shared pair, same cell prices
    duplicated, sharing forbidden: 2 OR2 + 2 AND2 = 2 x 3 + 2 x 3 = 12 area units
    shared, one OR built once: 1 OR2 + 2 AND2 = 3 + 6 = 9 area units
    saving = 12 - 9 = 3 area units, one OR2 not built

    The recorded generic netlist after optimisation holds 1 OR cell and 2 AND cells: the duplicate OR merged exactly as the hand count assumed. Mapping then reaches area 8 with the histogram 2 x INV, 3 x NOR2. The recorded mapped netlist holds no OR2 cell at all: ABC reworked the cone during mapping, so read its cover on its own terms rather than as your factored form. Either way, the sharing decision belongs to the generic stage, where identical inputs are easy to match, and mapping only prices what sharing left behind.

    Hierarchy hides sharing

    The sharing demo above hides a step: both copies of (b | c) sit in one module, so elaboration already exposes them. Real RTL often parks one copy behind a submodule boundary, where no generic pass merges through. The script below does that on purpose, records the hierarchical netlist, then dissolves the boundary with flatten and optimises again. That is sharing across hierarchy, not Boolean collapsing: collapsing means substituting one expression into another so new factors appear, while here the factor already exists twice and only the boundary stops the merge.

    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
    read_verilog <<EOV
    module sh(input wire a, input wire b, input wire c, output wire y1);
    assign y1 = a & (b | c);
    endmodule
    module collapse(input wire a, input wire b, input wire c, input wire d, output wire y1, output wire y2);
    sh s (.a(a), .b(b), .c(c), .y1(y1));
    assign y2 = d & (b | c);
    endmodule
    EOV
    hierarchy -check -top collapse
    proc
    design -save pre
    tee -o stat_col_hier.txt stat
    write_json net_col_hier.json
    design -load pre
    flatten
    delete t:$scopeinfo
    opt
    check
    tee -o stat_col_flat.txt stat
    write_json net_col_flat.json
    select -assert-count 1 t:$or
    select -assert-count 2 t:$and
    techmap
    tee -o abc_col.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_col.txt stat -liberty course.lib
    write_json net_col.json
    select -assert-none t:$and t:$or t:$not
    check
    log PASS: flattening exposes the shared factor for one OR and two ANDs

    Stage

    Generic cells

    Mapped cover

    Hierarchical, boundary intact

    2 AND plus 2 OR

    not mapped yet

    Boundary dissolved, re-optimised

    2 AND plus 1 OR

    not mapped yet

    Mapped cover (ABC, course.lib)

    no generic gates left

    2 x INV, 3 x NOR2, area 8

    Read the middle row as the payoff: dissolving the boundary exposes the duplicate the hierarchy hid, and the merge pass harvests it, leaving 2 AND cells and 1 OR cell where the hierarchy kept 2 ANDs and 2 ORs in separate modules. Mapping then covers the shared structure once, and the recorded cover is the receipt.

    Proof in Python

    The program below checks all 8 input rows for equality. It also counts literals from the expression trees and requires the factored form to be smaller.

    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
    45
    import sys
    
    def f_orig(a, b, c):
        return (a and b) or (a and c) or (b and c)
    
    def f_fact(a, b, c):
        return (a and (b or c)) or (b and c)
    
    def count_lits(node):
        if isinstance(node, str):
            return 1
        return sum(count_lits(k) for k in node[1:])
    
    ORIG = ("or", ("and", "a", "b"), ("and", "a", "c"), ("and", "b", "c"))
    FACT = ("or", ("and", "a", ("or", "b", "c")), ("and", "b", "c"))
    
    rows = 0
    diff = 0
    for a in (0, 1):
        for b in (0, 1):
            for c in (0, 1):
                rows += 1
                if bool(f_orig(a, b, c)) != bool(f_fact(a, b, c)):
                    diff += 1
    
    no = count_lits(ORIG)
    nf = count_lits(FACT)
    
    if rows != 8:
        print("FAIL: walked " + str(rows) + " rows, want 8")
        sys.exit(1)
    if diff != 0:
        print("FAIL: " + str(diff) + " rows differ")
        sys.exit(1)
    if no != 6:
        print("FAIL: original counts " + str(no) + " literals, want 6")
        sys.exit(1)
    if nf != 5:
        print("FAIL: factored counts " + str(nf) + " literals, want 5")
        sys.exit(1)
    if not nf < no:
        print("FAIL: factored form is not smaller")
        sys.exit(1)
    
    print("PASS: factored form agrees on 8 of 8 rows with 5 literals against 6")

    Break it: take away the library

    Now change the setup the other way: keep the majority function but map it against nand_only.lib, which stocks only inverters, buffers and 2-input NANDs. Every AND and OR in the design must now be built out of NANDs, so the cover needs more cells. The script maps the identical design and records the restricted area.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    read_verilog <<EOV
    module maj3(input wire a, input wire b, input wire c, output wire y);
    assign y = (a & b) | (a & c) | (b & c);
    endmodule
    EOV
    hierarchy -check -top maj3
    proc
    opt
    techmap
    tee -o abc_nand.txt abc -liberty nand_only.lib -constr typical.constr -D 1000
    tee -o stat_nand.txt stat -liberty nand_only.lib
    write_json net_nand.json
    select -assert-none t:$and t:$or t:$not
    select -assert-min 1 t:NAND2
    check
    log PASS: majority also maps to NAND-only cells at higher cost

    The restricted cover reaches area 10 against 7 on the full library. Read the two recorded histograms against each other: the full-library cover is 2 x NAND2, 1 x OAI21, while the restricted cover rebuilds the same function as 2 x INV, 4 x NAND2. Without the complex cells ABC pays extra NANDs and inverters, and the area gap between the two covers is that tax.

    Common mistakes

    Fewer literals need not mean less area or delay. Factoring can add levels, and collapsing can duplicate terms before anything improves. Price the factored form by hand, read the recorded mapped area, and believe the report when the two disagree: the report priced covers you never drew.

    Collapsing everything flat and hoping the tool recovers is slow and often worse. Collapse locally where sharing is visible, then let mapping decide. And never quote a hand count as the mapped result: here the hand says 12 and the recorded cover decides.

    Likely probes: literal arithmetic and the equivalence argument.

    • Weak: factoring uses fewer gates, so it is always better.

    • Strong: majority goes from 6 literals to 5, hand-priced 15 to 12 area units, while ABC records 7 because it covers from the whole library including complex cells. Sharing saves the repeated term once per extra user: here two written ORs merge to 1. Equivalence is exhaustive over all 8 rows, and the poorer library records 10 against 7.

    • Follow-up: the tool beats your hand count. Do you take the win or audit the cover, and what in the histogram decides?

    Check yourself The factored form uses one fewer AND gate. What is shared, and which two gates does it feed?

    The inputs b and c are shared. Each drives the OR forming b + c and the AND forming bc.

    Check yourself ABC records an area different from your hand count of 12. What explains the gap, either way?

    Different covers. The recorded histogram 2 x NAND2, 1 x OAI21 names the cells ABC actually chose, including complex ones the AND2 and OR2 hand arithmetic never priced.

    • Factoring trades literals against depth; collapsing only pays when refactoring follows.

    • Sharing is decided at the generic stage, where identical inputs are easy to match.

    • The mapped histogram names the cover that won, including complex cells the hand count never priced.

    Rule of thumb: price by hand, then believe the recorded report when the two disagree.

    How this was checked

    Every number and picture on this page comes from Yosys 0.69+post with ABC plus a standard-library Python check, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): majority and shared-pair designs through opt and abc -liberty course.lib, a submodule-hiding design through flatten, opt and abc -liberty course.lib, majority against nand_only.lib, and an exhaustive 8-row Python equivalence proof. Both teaching libraries are fictional, so areas are teaching units.

    • Interview: run a mock interview on this topic

    Related topics

    • Previous: Redundancy removal

    • Next: AIGs, depth and area

    Previous

    19 / 139

    Next: AIGs, Depth and Area

    Up next

    Up nextAIGs, Depth and Area

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Constant Propagation
    • Don't-Cares