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
  • Constraints and Timing15
  • Sequential, FSM and Memory19
    • Flops, Resets and Enables
    • FSM Synthesis
    • Memory and Macro Inference
    • Retiming and Pipelining
      • What Retiming Moves
      • Latency, Throughput and Register Count
      • Reset, Initial State and Equivalence
      • Pipelining a Long Path by Hand
      • A Retiming Model and the Tool Flow
  • Datapath Synthesis14
  • Area, Power and Test14
  • Verification, ECO and Handoff15
  • Flows and Debugging15
  • Interview and Judgment11
Loading lesson

←Retiming and Pipelining
  1. Topics
  2. Logic Synthesis
  3. Sequential, FSM and Memory
  4. Retiming and Pipelining
  5. Latency, Throughput and Register Count
On this page

    Part 4 of 9, lesson 16 of 19Part 4 of 9/Sequential, FSM and Memory/Lesson 16 of 19Intermediate

    Latency, Throughput and Register Count

    Latency stays fixed while throughput rises, and fanout moves change the register count.

    A flop duplicated across a fanout looks like added latency to anyone counting totals. It is not: each branch still crosses one flop, and the price is area, not cycles.

    After this lesson you can: read a fanout move in both directions, and state latency, throughput, and flop count for each side.

    Latency is cycles, throughput is rate

    Latency counts register stages from input to output. Throughput counts results per second after the pipeline fills: one result per clock cycle divided by the achieved period. Retiming keeps every path register count, so latency is unchanged; a shorter period then raises throughput at the same latency. Pipelining is the opposite bargain: it adds register stages, so latency grows by one cycle per cut even as throughput rises.

    One flop at a fanout stem

    Both designs below compute the same AND of two bits and broadcast it to two outputs. The only difference is the register boundary: one flop sits at the fanout stem, or one flop sits on each branch. The script counts both, maps both against the teaching library, and proves the pair equivalent over 4 compared steps. Read the two netlists as one move in two directions: forward across the stem duplicates the flop, backward merges the pair.

    Predict first The stem flop fans out to two branches. Moving it forward across the stem: does the total rise, and does any path gain a cycle?

    The total rises from 1 to 2: one flop per branch. No path gains a cycle: each branch still crosses exactly one flop, so latency stays at 1 cycle and only the count moves.

    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
    read_verilog <<EOV
    module fanA(input clk, input a, b, output y1, y2);
      reg r;
      wire t;
      assign t = a & b;
      always @(posedge clk) r <= t;
      assign y1 = r;
      assign y2 = r;
    endmodule
    module fanB(input clk, input a, b, output y1, y2);
      reg q1, q2;
      wire t;
      assign t = a & b;
      always @(posedge clk) begin q1 <= t; q2 <= t; end
      assign y1 = q1;
      assign y2 = q2;
    endmodule
    EOV
    hierarchy -check
    proc
    opt_clean
    design -save all
    hierarchy -check -top fanA
    tee -o stat_A.txt stat
    select -assert-count 1 t:$dff
    techmap
    opt_clean
    tee -o abc_A.txt abc -liberty course.lib -constr typical.constr -D 100000
    dfflibmap -liberty course.lib
    tee -o statA_lib.txt stat -liberty course.lib
    write_json net_A.json
    design -load all
    hierarchy -check -top fanB
    tee -o stat_B.txt stat
    select -assert-count 2 t:$dff
    techmap
    opt_clean
    tee -o abc_B.txt abc -liberty course.lib -constr typical.constr -D 100000
    dfflibmap -liberty course.lib
    tee -o statB_lib.txt stat -liberty course.lib
    write_json net_B.json
    design -load all
    miter -equiv fanA fanB miter
    hierarchy -check -top miter
    flatten
    tee -o equiv_fan.txt sat -seq 4 -prove trigger 0 -verify -set-init-zero
    design -load all
    hierarchy -check -top fanB
    opt
    tee -o stat_Bopt.txt stat
    select -assert-count 1 t:$dff
    log PASS: fanout pair counted, mapped and proved equivalent over 4 cycles

    What the two runs recorded

    The stem variant holds 1 flop and the branched variant holds 2 flops; both map the AND to area 3 at a recorded combinational arrival of 133.96 ps, so either side offers one result per cycle at that arrival. That arrival is an ABC combinational number, not a clock period: with no flop clk-to-q or setup in it, no reachable frequency follows. With flops mapped to library cells the chip area moves from 11 to 19, a gap of exactly one DFF cell. The sequential check proves the two streams equal on all 4 compared steps from the zeroed state: same latency, same rate, different count.

    Stem variant: the AND feeds 1 flop, whose output fans out to both branches. Chip area 11 with the flop mapped.
    Branched variant: the AND fans out first and each branch holds its own flop, 2 flops in total. Chip area 19.

    Variant

    Latency (cycles)

    Throughput (results per cycle)

    Flops

    Chip area

    Stem (1 flop)

    1

    1 at 133.96 ps arrival

    1

    11

    Branches (2 flops)

    1

    1 at the same arrival

    2

    19

    Read the table in both directions. Forward across the stem duplicates the flop and the count rises; backward merges the pair and the count falls. Neither direction touches latency or throughput at the I/O boundary: each branch still crosses one flop and the period never moves. Pipelining, by contrast, would add a cycle per new stage; the program below pins that difference with plain assertions on a classroom model.

    The backward reading deserves a second look, because it is the direction a min-area tool pass attempts on its own: two flops doing the same job are one flop too many, and the stem is the single point that reaches both branches. The run above did the merge by hand so both directions could be counted, but the economics are the tool's: whenever two branch flops sample the same net under the same clock, merging them backward saves a flop without touching any path. The period stands still for a simpler reason than any algorithm: both variants map the same single AND against the same load, so the same gate sets the same arrival either way.

    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
    import sys
    
    period = 6.0
    cuts = 2
    
    stem_regs = ["r"]
    branch_regs = ["q1", "q2"]
    path_regs_stem = [1, 1]
    path_regs_branched = [1, 1]
    
    flops_before = len(stem_regs)
    flops_after = len(branch_regs)
    lat_before = max(path_regs_stem)
    lat_after_retime = max(path_regs_branched)
    lat_after_pipe = lat_before + cuts
    thr_before = 1.0 / period
    thr_after = 1.0 / 6.0
    
    problems = []
    if lat_before != 1:
        problems.append("stem latency is 1, got %s" % lat_before)
    if lat_after_retime != 1:
        problems.append("branched latency is 1, got %s" % lat_after_retime)
    if lat_after_retime != lat_before:
        problems.append("retiming must not change latency")
    if lat_after_pipe != 3:
        problems.append("pipelining latency is 3, got %s" % lat_after_pipe)
    if abs(thr_before - thr_after) > 1e-12:
        problems.append("throughput must hold at a fixed period")
    if flops_before != 1 or flops_after != 2:
        problems.append("flop counts are 1 and 2, got %s %s" % (flops_before, flops_after))
    if flops_after - flops_before != 1:
        problems.append("fanout duplication adds one flop")
    
    if not problems:
        print("PASS: latency 1 stays 1 across retiming and 1 plus 2 across pipelining, flops 1 to 2")
        sys.exit(0)
    print("FAIL: " + ", ".join(problems))
    sys.exit(1)

    Break it: let full opt merge the twins

    The script deliberately uses opt_clean instead of opt so the twin registers survive to be counted. The final section is the changed variant: full opt on the branched design merges the identical flops and the report falls back to 1 flop. The merge is legal here because both flops sample the same net under the same clock with no reset or enable, but it destroys the very thing the lesson counts. Count first with opt_clean, optimise after.

    Common mistakes

    Counting total registers instead of per-path registers misleads. Duplication across fanout raises the total while every path still crosses one register; always recount each branch separately.

    Latency against throughput, and who pays for balance.

    • Weak answer: more flops means more latency, so the branched variant is slower.

    • Strong answer: latency is path stages, not totals: both variants hold 1 cycle per branch and 1 result per cycle at the same recorded arrival. The only price of the forward move is the count, 1 to 2, worth 8 area units for the extra library flop.

    • Follow-up: the two branches later need different logic. Does the merge still hold? No: once the flops sample different nets they are no longer twins, and neither opt nor backward retiming may merge them.

    Check yourself Both variants map the AND to the same recorded arrival in the table above. The branched variant holds 2 flops. What is its throughput, and what did the extra flop buy?

    One result per cycle at that same arrival, exactly the stem variant's rate. The extra flop bought nothing for speed here; it is the structural price of pushing the boundary across the fanout.

    Check yourself Full opt merges the branched pair back to 1 flop. Why is the merge legal, and why does the script avoid it?

    Both flops sample the same net under the same clock with no reset or enable, so they always agree. The script uses opt_clean because the lesson counts the unmerged pair; merging first would erase the measurement.

    • Latency is path stages; throughput is results per second at the achieved period.

    • Forward across a stem duplicates the flop; backward merges the pair; neither touches latency.

    • Count with opt_clean when twins must survive; full opt merges identical flops and erases the count.

    Rule of thumb: recount per branch after any fanout move, and re-read the period before claiming a throughput change.

    How this was checked

    Flop counts, ABC delays, library-cell areas, netlist pictures, and the 4-step equivalence result on this page come from one Yosys 0.70 run of the script shown, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): both fanout variants counted, mapped against course.lib with typical.constr, flops covered by dfflibmap, and proved equal by sat. The latency and pipelining relationships are asserted by the stdlib-only Python model on the page, labelled so; the library is fictional, so absolute areas and delays are teaching units.

    • Interview: run a mock interview on this topic

    Related topics

    • What retiming moves

    • Delay models and slack

    Previous

    67 / 139

    Next: Reset, Initial State and Equivalence

    Up next

    Up nextReset, Initial State and Equivalence

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Pipelining a Long Path by Hand
    • A Retiming Model and the Tool Flow