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
      • Clocks and Generated Clocks
      • Uncertainty, Latency and IO Delays
      • Driving Cells, Loads and Units
      • False, Multicycle and Max-Delay Paths
      • Missing and Over-Tight Constraints
    • Delay Models and Slack
    • 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

←SDC for Synthesis
  1. Topics
  2. Logic Synthesis
  3. Constraints and Timing
  4. SDC for Synthesis
  5. Missing and Over-Tight Constraints
On this page

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

    Missing and Over-Tight Constraints

    Unconstrained paths, over-constraining, and a tiny SDC subset checker.

    An empty timing report looks clean and a tighter target looks diligent, but unconstrained paths report nothing and past-the-wall targets spend area for identical arrivals. One sweep plus one checker teaches both verdicts.

    After this lesson you can: run a coverage check over clock and IO constraints, read an area-against-delay plateau, and tell a missing target from an over-tight one.

    The question

    Constraints fail in two opposite directions, and both look innocent in the file. A missing constraint leaves its paths without a target: the tool optimises them blind and reports nothing, which is worse than a bad number. An over-tight constraint states a target fiercer than the requirement: the tool spends area chasing it, and past what the library can give, the spending continues while the delay stops moving. This lesson records both on one design: a four-target sweep plus a checker that catches the missing half mechanically.

    Port directions hold here: req, data and valid are inputs, ack is an output. The checker enforces exactly that split, so ack is never asked for an input delay.

    One cone, four targets

    The design is an eight-input cone of AND, OR and XOR terms feeding one output, big enough to need bigger cells and small enough to draw. The script synthesises it once, saves the generic gates, then maps the same gates four times against sized.lib: a no-pressure run at 100000 ps standing in for a missing target, a middle run, a tight run already past what the library can give, and a past-the-wall run further still. Every run writes its ABC report and stat histogram; the loose and tight runs also save their netlists. The assertions prove the loose run uses no upsized cell and the tight and past-the-wall runs do.

    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
    read_verilog <<EOV
    module budget8(input [7:0] x, output y);
    assign y = ((x[0] & x[1]) | (x[2] & x[3]) | (x[4] ^ x[5])) & (x[6] | x[7]);
    endmodule
    EOV
    synth -top budget8 -flatten
    design -save generic
    tee -o abc_loose.txt abc -liberty sized.lib -constr typical.constr -D 100000
    tee -o stat_loose.txt stat -liberty sized.lib
    write_json net_loose.json
    select -assert-none t:*_X2 t:*_X4
    design -load generic
    tee -o abc_mid.txt abc -liberty sized.lib -constr typical.constr -D 300
    tee -o stat_mid.txt stat -liberty sized.lib
    design -load generic
    tee -o abc_tight.txt abc -liberty sized.lib -constr typical.constr -D 200
    tee -o stat_tight.txt stat -liberty sized.lib
    write_json net_tight.json
    select -assert-min 1 t:*_X2 t:*_X4 %u
    design -load generic
    tee -o abc_past.txt abc -liberty sized.lib -constr typical.constr -D 100
    tee -o stat_past.txt stat -liberty sized.lib
    select -assert-min 1 t:*_X2 t:*_X4 %u
    log PASS: loose meets no pressure and past the wall buys nothing

    Four targets, one variable: the number after -D. Same RTL, same library, same load throughout, so every difference between the reports is the target talking.

    What the runs recorded

    Predict first You ask for 100 ps after seeing the 200 ps result. Will ABC hand you a faster netlist, or the same one back?

    The same one. The 200 ps run already misses at 211 ps against its own target, and the 100 ps run returns exactly those figures: both targets sit past the wall.

    Target -D (recorded)

    Cells (recorded)

    Area (recorded)

    Delay reached (recorded)

    Upsized cells (recorded)

    100000 ps

    5

    16

    342 ps

    0

    300 ps

    5

    20.8

    298 ps

    1

    200 ps

    5

    33.4

    211 ps

    4

    100 ps

    5

    33.4

    211 ps

    4

    The loose end is the cheapest cover: 5 cells at area 16 and 342 ps, because nothing asked for speed. The middle run pays a little for a little, meeting its target at area 20.8 and 298 ps. The tight run pays 109% more area than the loose end yet still misses at 211 ps. Then the past-the-wall run returns the tight netlist unchanged: pressure past the wall buys no more speed, and the 3 distinct netlists mark each step the tool could take.

    Recorded area against delay: area climbs steeply, then the last two targets land on the same point.
    Recorded no-pressure netlist: the cheapest cover, no upsized cell.
    Recorded tight-target netlist: the same cone with upsized cells highlighted.

    Unconstrained means unreported

    A path with no clock or IO constraint covering it is unconstrained, and an unconstrained path is not timed: it appears in no timing report, with no slack, comfortable or otherwise. An empty report looks clean but is not: a path the tool never checked can fail on silicon without ever failing in the flow. The usual causes are a missing clock, a missing input or output delay, or an over-wide exception. In a commercial flow the mechanical way to find them is check_timing, or report_checks -unconstrained; not run here.

    Coverage matters as much as values. Every input port needs its input delay or a documented reason it has none; every output port needs its output delay; every clock needs its period. The program below checks exactly that coverage on a small SDC subset, keeping ack an output: it asks for an output delay on ack, never an input delay.

    A tiny constraint checker

    The program parses create_clock lines with their periods and input and output delay lines with their ports, then reports which ports of the input and output lists have no delay. Its fixed EXAMPLE parameters cover three inputs and two outputs against four constraint lines. Eight self-checks pin the parsed clock, both constrained sets, both missing sets, and the recorded wall: both tight targets miss and the past run lands on the same arrival.

    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
    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
    import sys
    
    # Tiny SDC-subset coverage checker plus a slack verdict against the recorded
    # sweep. Fixed EXAMPLE parameters of the program, hand-derived, except the
    # two arrivals, which are interpolated from the recorded ABC delays when the
    # page is built. ack is an output everywhere in this topic; req, data and
    # valid are inputs.
    SDC = [
        "create_clock -name CLK -period 10.0 [get_ports clk]",
        "set_input_delay -clock CLK 3.0 [get_ports req]",
        "set_input_delay -clock CLK 3.0 [get_ports data]",
        "set_output_delay -clock CLK 2.5 [get_ports ack]",
    ]
    
    INPUTS = ["req", "data", "valid"]
    OUTPUTS = ["ack", "done"]
    
    def ports_in(line):
        start = line.find("[get_ports ")
        if start < 0:
            return []
        inner = line[start + len("[get_ports "):]
        end = inner.find("]")
        if end < 0:
            return []
        return inner[:end].split()
    
    clocks = {}
    in_hit = set()
    out_hit = set()
    for line in SDC:
        text = line.strip()
        if text.startswith("create_clock"):
            parts = text.split()
            clocks[parts[parts.index("-name") + 1]] = float(parts[parts.index("-period") + 1])
        if text.startswith("set_input_delay"):
            for p in ports_in(text):
                in_hit.add(p)
        if text.startswith("set_output_delay"):
            for p in ports_in(text):
                out_hit.add(p)
    
    missing_in = [p for p in INPUTS if p not in in_hit]
    missing_out = [p for p in OUTPUTS if p not in out_hit]
    
    if clocks != {"CLK": 10.0}:
        print("FAIL: clocks parsed as " + str(clocks))
        sys.exit(1)
    if sorted(in_hit) != ["data", "req"]:
        print("FAIL: input constrained parsed as " + str(sorted(in_hit)))
        sys.exit(1)
    if sorted(out_hit) != ["ack"]:
        print("FAIL: output constrained parsed as " + str(sorted(out_hit)))
        sys.exit(1)
    if missing_in != ["valid"]:
        print("FAIL: missing inputs parsed as " + str(missing_in))
        sys.exit(1)
    if missing_out != ["done"]:
        print("FAIL: missing outputs parsed as " + str(missing_out))
        sys.exit(1)
    
    # Slack verdict against the recorded sweep: slack is required minus arrival,
    # in integer picoseconds, and its sign is the verdict. Both tight targets sit
    # past the library wall, so both must miss, and the past-the-wall run must
    # land on the same arrival as the tight one.
    TIGHT_TARGET_PS = 200
    PAST_TARGET_PS = 100
    TIGHT_ARRIVAL_PS = 211
    PAST_ARRIVAL_PS = 211
    
    tight_slack = TIGHT_TARGET_PS - TIGHT_ARRIVAL_PS
    past_slack = PAST_TARGET_PS - PAST_ARRIVAL_PS
    if tight_slack >= 0:
        print("FAIL: tight slack is " + str(tight_slack) + " ps, want a miss")
        sys.exit(1)
    if past_slack >= 0:
        print("FAIL: past-the-wall slack is " + str(past_slack) + " ps, want a miss")
        sys.exit(1)
    if PAST_ARRIVAL_PS != TIGHT_ARRIVAL_PS:
        print("FAIL: plateau broken, past " + str(PAST_ARRIVAL_PS) + " vs tight " + str(TIGHT_ARRIVAL_PS))
        sys.exit(1)
    
    print("PASS: 1 of 3 inputs and 1 of 2 outputs lack a delay and the 200 ps target already misses")

    Two gaps: valid lacks an input delay, done lacks an output delay. Both ports' paths are unconstrained and escape every timing report. A checker this small belongs in every flow: coverage as a verdict with port names in it.

    The slack table

    Slack is required minus arrival, and its sign is the verdict. The hand-derived table below fixes an EXAMPLE arrival of 7.0 ns: against 10.0 ns the slack is +3.0 ns, comfortable; against 6.0 ns it is -1.0 ns, a violation. Only the requirement moved, which is precisely how over-constraining manufactures failures out of passing paths.

    Requirement (hand-derived)

    Arrival (hand-derived)

    Slack (hand-derived)

    10.0 ns

    7.0 ns

    +3.0 ns (meets)

    6.0 ns

    7.0 ns

    -1.0 ns (violated)

    Hold that sign flip against the sweep table above. The tight run's extra area bought the last real speed-up, but even its requirement was already past the wall: it reaches 211 ps, a miss the program asserts. The past-the-wall run misses by further and reports the same arrival, because the library had nothing left to sell. Over-constraining is a requirement error wearing a timing report as camouflage.

    Break it: ask past the wall

    The last run in the script is the break-it: the same cone past the wall after the tight run already missed its own target. Nothing else changed: the tight netlist back, cell for cell. When delay stops improving while you keep asking for more, change the logic, the library, or the requirement.

    Symptom

    Cause

    Fix

    Paths missing from every report

    Missing clock, IO delay, or an over-wide exception

    Add the missing bound; narrow the exception; run the checker.

    Worse area, same real slack

    Target tighter than the requirement

    Constrain the real target; move margin to variation.

    Silent stale exceptions

    RTL changed under old constraints

    Re-check exceptions after each RTL change.

    An over-wide false path hides a real path; a missing delay leaves one targetless. Both pass a glance at the file. Glance at values; check coverage with a tool.

    Name the two failure modes and how to catch each.

    • Weak answer: missing constraints show comfortable slack, and tighter targets always buy speed.

    • Strong answer: missing constraints show nothing at all, because unconstrained paths are not timed; the checker here finds valid and done uncovered. Over-tight targets spend area past the wall: the 200 ps run already misses at 211 ps and the past-the-wall run lands on the same arrival, while area climbs 109% from the loose row to the tight row. Catch the first with a coverage checker and the second by reading the area-against-delay table before tightening again.

    • Follow-up: the requirement was real and delay still misses. What now? The library is exhausted, so change the logic or the microarchitecture; no target fixes a wall.

    Check yourself One input port and one output port lack a delay. Name the failure mode, the ports, and what the tool reports.

    Unconstrained paths on valid and done: the tool reports nothing for them, neither slack nor violation.

    Check yourself The target is tightened and area grows while the arrival plateaus. What is the likely cause, and what does the sweep show?

    Over-constraining. The 200 ps run already misses at 211 ps and the 100 ps run returns the same netlist, so pressure past the wall buys nothing. Constrain the real target instead.

    • Unconstrained paths are not timed: they report nothing, never comfortable slack.

    • ack is an output everywhere in this topic; the checker asks an output delay of it, never an input delay.

    • Past what the library can give, tighter targets return the same netlist: change the logic, the library, or the requirement.

    Rule of thumb: check coverage mechanically first, then read the sweep before tightening anything.

    How this was checked

    Cell counts, areas, delays, histograms, and netlist pictures 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): ABC maps one cone against the teaching library sized.lib at four delay targets from no-pressure to past-the-wall. The library is fictional, so absolute areas and delays are teaching units; the plateau shape is the point. Coverage gaps and slack signs come from the stdlib-only Python program shown, which asserts every figure it prints, and are labelled hand-derived.

    • Interview: run a mock interview on this topic

    Related topics

    • Clocks and Generated Clocks

    • Delay Models and Slack

    • See also: Setup, Hold and Slack

    Previous

    41 / 139

    Next: Delay Models and Slack

    Up next

    Up nextDelay Models and Slack

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Clocks and Generated Clocks
    • Uncertainty, Latency and IO Delays
    • Driving Cells, Loads and Units