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
    • SDC for Synthesis
    • Delay Models and Slack
      • Arrival, Required Time and Slack
      • Setup, Hold and Clock-to-Q
      • Cell Delay Models
      • Wire Delay at Synthesis Time
      • A Static Timing Calculator
    • Timing-Driven Synthesis
  • Sequential, FSM and Memory19
  • Datapath Synthesis14
  • Area, Power and Test14
  • Verification, ECO and Handoff15
  • Flows and Debugging15
  • Interview and Judgment11
Loading lesson

←Delay Models and Slack
  1. Topics
  2. Logic Synthesis
  3. Constraints and Timing
  4. Delay Models and Slack
  5. Cell Delay Models
On this page

    Part 3 of 9, lesson 8 of 15Part 3 of 9/Constraints and Timing/Lesson 8 of 15Intermediate

    Cell Delay Models

    Table and linear cell-delay models compared at the same loads.

    A table lookup and a straight line can agree perfectly and still disagree everywhere it matters. The failure is invisible until both are evaluated at the same loads with the slew stated.

    After this lesson you can: interpolate an NLDM table bilinearly, fit a linear model at stated conditions, and chart where the fit errs.

    The question

    A path delay is the sum of its stage delays, each from a library model fed with that stage's input slew and output load. The library model is a table (NLDM): delay looked up by slew and load, interpolated between grid points. The cheaper alternative is linear: a base delay plus a load coefficient, no table. This lesson compares the two at the same loads on the first stage of the shared path, and charts where the linear fit stops telling the truth.

    The stage under test is the NAND2 at the head of the vipexp path. Its course.lib cell_rise table is a two-by-two grid over 0.01-to-0.10 in both axes (slew in ns, load in pF): 0.08 and 0.10 ns on the fast-slew row, 0.11 and 0.14 ns on the slow-slew row. The program below copies those rows exactly and interpolates bilinearly, so every table number here is a library value, not an illustration.

    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
    read_verilog <<EOV
    module vipexp(input clk, input a, b, c, d, input din, output reg qout, output t1, t2, t3, t4);
      reg q1;
      wire s1, s2, s3, s4;
      always @(posedge clk) q1 <= din;
      assign s1 = ~(q1 & a);
      assign s2 = ~(s1 | b);
      assign s3 = s2 ^ c;
      assign s4 = ~(s3 ^ d);
      always @(posedge clk) qout <= s4;
      assign t1 = s1;
      assign t2 = s2;
      assign t3 = s3;
      assign t4 = s4;
    endmodule
    EOV
    hierarchy -check -top vipexp
    synth -top vipexp -flatten
    dfflegalize -cell $_DFF_P_ 01 -cell $_DFF_PN0_ 01
    design -save generic
    dfflibmap -liberty course.lib
    tee -o abc_typ.txt abc -liberty course.lib -constr typical.constr -D 1000
    clean
    tee -o stat_typ.txt stat -liberty course.lib
    write_json net_typ.json
    select -assert-none t:$not t:$and t:$or t:$xor t:$mux t:$_NOT_ t:$_AND_ t:$_OR_ t:$_XOR_ t:$_NAND_ t:$_NOR_ t:$_DFF_P_ t:$_MUX_
    select -assert-count 2 t:DFF
    select -assert-count 1 t:NAND2
    select -assert-count 1 t:NOR2
    select -assert-count 1 t:XOR2
    select -assert-count 1 t:XNOR2
    check
    design -load generic
    dfflibmap -liberty course.lib
    tee -o abc_narrow.txt abc -liberty nand_only.lib -constr typical.constr -D 1000
    clean
    tee -o stat_narrow.txt stat -liberty nand_only.lib
    select -assert-count 2 t:DFF
    select -assert-min 8 t:NAND2
    check
    log PASS: table model path mapped against two libraries

    The script maps vipexp twice: against the full teaching library and against nand_only.lib, where every gate must be built from inverters and two-input NANDs. The full-library run reports 4 cells at area 14 with arrival 610 ps. The narrow run reports 14 cells at area 22 with arrival 737 ps. Same RTL, restricted cells, visibly slower: the delay model only matters through the cells the library offers.

    Predict first The narrow library replaces every complex stage with NAND2 and INV networks. Will the NAND2 table below still price those stages, and will the arrival rise or fall?

    The table still prices each NAND2 stage, but there are now more stages to price: the recorded histogram holds 8 NAND2 cells against one before, so the arrival rises from 610 ps to 737 ps. Same tables, more of them.

    Reading the table at fixed slew

    Fix the input slew at 0.05 ns and vary only the load, and the bilinear interpolation collapses to a straight line between the two load columns: each load fraction blends the fast-column and slow-column values at that slew. That exact straightness is a property of this 2-by-2 teaching grid, not of tables in general: production NLDM tables are about 7-by-7 and visibly nonlinear in load. The five loads below span the load axis, and the program asserts every interpolated value against hand arithmetic, plus the trustworthiness properties: grid points return their entries, the midpoint is the mean of the four corners, and delay rises monotonically both ways.

    A linear fit through the two endpoints of that line (base 0.093 ns plus 0.27 ns per pF) matches the table everywhere on it, by construction. That is the fair comparison: both evaluated at the same loads with the stated slew. Where the fit holds, and where it fails, is what the next section measures.

    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
    
    # NAND2 cell_rise rows copied exactly from scripts/tools/synth-libs/course.lib.
    # Grid: slew 0.01 and 0.1 ns (rows) against load 0.01 and 0.1 pF (columns).
    TABLE = [[0.08, 0.10], [0.11, 0.14]]
    SLEW = 0.05
    LOADS = [0.01, 0.03, 0.05, 0.07, 0.10]
    # Hand arithmetic for the five loads at the fixed slew (ns).
    HAND_HOME = [0.093333, 0.098765, 0.104198, 0.109630, 0.117778]
    
    def bilin(table, slew, load):
        (v00, v01), (v10, v11) = table
        fs = (slew - 0.01) / 0.09
        fl = (load - 0.01) / 0.09
        return v00 * (1 - fs) * (1 - fl) + v01 * (1 - fs) * fl + v10 * fs * (1 - fl) + v11 * fs * fl
    
    def close(got, want, tol=1e-9):
        return abs(got - want) <= tol
    
    # Linear fit through the two endpoints at the home slew.
    base = bilin(TABLE, SLEW, 0.01)
    k = (bilin(TABLE, SLEW, 0.10) - base) / 0.09
    fit = lambda load: base + k * (load - 0.01)
    
    bad = 0
    def check(name, cond):
        global bad
        if not cond:
            print("FAIL: " + name)
            bad += 1
    
    check("grid point returns entry", close(bilin(TABLE, 0.01, 0.01), 0.08))
    check("grid point returns entry", close(bilin(TABLE, 0.1, 0.1), 0.14))
    check("midpoint is the mean", close(bilin(TABLE, 0.055, 0.055), (0.08 + 0.10 + 0.11 + 0.14) / 4))
    check("monotone in load", bilin(TABLE, 0.05, 0.1) > bilin(TABLE, 0.05, 0.01))
    check("monotone in slew", bilin(TABLE, 0.1, 0.05) > bilin(TABLE, 0.01, 0.05))
    for load, want in zip(LOADS, HAND_HOME):
        check("table at load " + str(load), abs(bilin(TABLE, SLEW, load) - want) < 0.000001)
        check("fit exact on home curve", close(fit(load), bilin(TABLE, SLEW, load)))
    gap = min(bilin(TABLE, 0.1, load) - fit(load) for load in LOADS)
    check("fit errs off its home slew", gap > 0.015)
    if bad != 0:
        sys.exit(1)
    print("PASS: table versus linear at the same loads, fit error above 0.015 ns elsewhere")

    Where the linear fit errs

    Now move the slew to 0.10 ns and keep the same five loads and the same fit. This lesson's linear fit has no slew term, a deliberate strawman: real Liberty linear models (generic_cmos) include slope sensitivity. So the fit predicts exactly what it predicted before, while the table rises everywhere, because a slower input edge delays the output at every load. The chart below pairs them at each load.

    At the slow input slew the table values sit 0.017 to 0.022 ns above the linear fit at every one of the five shared loads; the fit, built at 0.05 ns, cannot see the slew change.

    At 0.01 pF the table says 0.110 ns and the fit says 0.093 ns; at 0.10 pF the table says 0.140 ns and the fit says 0.118 ns. The gap never drops below 0.015 ns across the five shared loads, which the program asserts. The fit is not wrong about loads: on its home slew it is exact. It is blind to slew, and this is the price of that blindness at identical loads.

    Break it: take away the cells

    The narrow-library run is the deliberate breakage: same RTL, target and loads, but only NAND2 and INV cells to build with. ABC rebuilds the path as 14 cells at area 22 with arrival 737 ps, against 4 cells at area 14 with arrival 610 ps free. The NOR2, XOR2 and XNOR2 stages each become small NAND networks, so the priced path gets longer while every price still comes from a table.

    The honest boundary: a better model of the wrong cells still loses to the right cells. Model fidelity and library richness are independent axes, and the two runs move only the second.

    NLDM against CCS, in one paragraph

    The tables here are NLDM: one delay number per (slew, load) grid point, interpolated between points. Composite current-source models (CCS) store current waveforms instead and integrate them against the load, tracking waveform shape more faithfully at the cost of bigger libraries and slower analysis. Nothing here runs CCS: the open tools read NLDM-style tables only, so the comparison is stated, not measured, and every number above is NLDM.

    Common mistakes

    Quoting one delay number per cell ignores the slew and load it was measured at. The NAND2 on this page is 0.08 ns at the fast corner and 0.14 ns at the slow corner: same cell, seventy-five percent apart, both straight from the table.

    When does a linear delay model fail, and how do you prove it?

    Weak: linear models are less accurate than tables. Strong: the fit here is exact at its home slew across all five shared loads, but at the slow slew the table exceeds it by 0.017 to 0.022 ns at every load, and the program asserts the minimum gap stays above 0.015 ns: this fit has no slew term, so prove the failure by moving the slew, not the load. Follow-up: the error budget is 0.010 ns. Can you still use the fit?

    Answer: only where the slew stays within a hair of the home value, and even then recheck at signoff: a 0.017 ns blind spot already spends the whole budget.

    Check yourself Both slew and load fractions are one half in a table query. Why is the answer the plain mean of the four entries?

    Bilinear weights are products of one half and one half, so each corner weighs one quarter. Four equal quarters give the mean, exactly as the program's midpoint check confirms.

    Check yourself The narrow run reports 14 cells against 4 free. What explains the slower arrival, and what stayed the same?

    More stages to price, each still from its table. What stayed the same is the method: identical loads, target and interpolation, so the 127 ps gap is pure library restriction.

    • Compare table and linear at the same loads with the slew stated, or the comparison proves nothing.

    • At fixed slew the table is linear in load; the linear form goes blind when the slew moves.

    • NLDM stores delays per grid point; CCS stores current waveforms instead (stated, not run).

    Rule of thumb: quote every cell delay with the slew and load it was read at.

    How this was checked

    Cell counts, areas and arrivals on this page come from one Yosys run of the script shown, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): the vipexp block mapped with ABC against course.lib and against nand_only.lib at a 1000 ps target with typical.constr. The library is fictional, so absolute delays are teaching values. Table values are exact course.lib rows interpolated by the stdlib-only program shown; the linear fit and its error are model numbers, labelled so on the page. CCS is described, not run.

    • Interview: run a mock interview on this topic

    Related topics

    • Wire Delay at Synthesis Time

    • A Static Timing Calculator

    • Cell Libraries and Liberty

    Previous

    44 / 139

    Next: Wire Delay at Synthesis Time

    Up next

    Up nextWire Delay at Synthesis Time

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Arrival, Required Time and Slack
    • A Static Timing Calculator