Point-to-Point & Combinational Delays
Point-to-Point & Combinational Delays
Most timing constraints work through clocks. You define a clock, and the tool figures out launch and capture edges. But some paths have no clock at either end, or you simply want to bound a specific path directly. For these cases the SDC constraint format offers set_max_delay and set_min_delay . They put a hard time limit on a path, independent of any clock. A combinational path is a path made only of gates, with no flip-flop in between. The signal flows straight through. Such a path may run from an input port to an output port with nothing clocked along the way. There is no edge to anchor it to, so a clock-based check does not apply. A direct delay bound does. This chapter covers when to bound a path directly instead of through clocks, how the two commands work, and where they fit best: pure combinational paths, certain I/O paths, and asynchronous datapaths. It is worth being precise about what "no clock" means. A register-to-register path has a flip-flop at each end, and each flip-flop is driven by a clock. The launch edge and the capture edge come from those clocks, so the tool derives the time budget automatically from the clock period. A combinational path has gates only, with no flip-flop to provide an edge. With nothing to launch from and nothing to capture
into, there is no period to lean on. The only way to give such a path a deadline is to state the number outright, and that is exactly what these two commands do.
When to Bound a Path Directly
Clock-based checks are the default and the right choice for register-to-register paths. But three situations call for a direct bound. First, a pure combinational path with no clock at either end. Second, an asynchronous datapath where the clock relationship is meaningless but you still want a maximum. Third, any path where you want a tighter or looser limit than the clock would impose, for a specific reason.
set_max_delay sets the largest time a path may take. It feeds the setup-like check: the path must
finish within the limit. set_min_delay sets the smallest time a path may take. It feeds the hold-like check: the path must not be faster than the limit. Together they define a window.
| Situation | Use | Why not clocks |
|---|---|---|
| Pure combinational in-to-out | set_max_delay | No flip-flop, no edge to anchor |

A combinational input-to-output path is the textbook case. Imagine an input port feeding a chain of gates that drives an output port, with no flip-flop anywhere. There is no clock edge to launch or capture, so the tool needs a direct number. set_max_delay 2.40 says the whole path must complete within 2.40 ns. You scope it with -from , -to , and -through , the same points used by other exceptions. For a full input-to-output bound you give the input port as -from and the output port as -to .
# standard SDC — portable across compliant tools
# Bound a pure combinational input-to-output path
set_max_delay 2.40 -from [get_ports sel_in] -to [get_ports decode_out]
# Tighter bound through a specific internal node
set_max_delay 1.85 -through [get_pins glue_logic/Z] -to [get_ports flag_out]

set_min_delay guards against a path being too fast. This matters for hold-like races, where a fast
path can let new data overtake old data before it is used. By setting a floor, you force the tool to keep a path at or above a minimum, inserting delay if needed. A realistic floor might be 0.30 ns on a combinational path that feeds a latch-like structure. If the path were faster, the downstream element could capture the wrong value. The min delay creates a safe window together with the max delay. Min-delay bounds are easy to overlook because most timing pressure pushes the other way: designers spend their effort making paths faster, and a fast path feels like a good thing. But fast is only good up to a point. In a race, the dangerous failure is a path that arrives too early, before the previous value has been safely used. That failure does not improve when you speed everything up; it gets worse. A min delay is the explicit guard, telling the tool that on this particular path, faster is not better below the floor you set.
# standard SDC — portable across compliant tools
# Keep a path from being too fast (race protection)
set_min_delay 0.30 -from [get_ports sel_in] -to [get_ports decode_out]
| Command | Bounds | Feeds | Failure if violated |
|---|---|---|---|
| set_max_delay | Longest time | Setup-like | Path too slow |
| set_min_delay | Shortest time | Hold-like | Path too fast (race) |
Worked Numerical Example
Take a combinational path bounded by set_max_delay 2.40 and set_min_delay 0.30 . The actual longest delay through the gates is 2.05 ns. The actual shortest delay is 0.42 ns.

Both pass, so the path's real delay sits safely inside the 0.30 ns to 2.40 ns window. If the longest delay had been 2.55 ns, max slack would be 2.40 - 2.55 = -0.15 ns, a failure, and the tool would speed the path up. If the shortest delay had been 0.18 ns, min slack would be 0.18 - 0.30 = -0.12 ns, and the tool would add delay.

Point-to-point delays also help at the chip boundary in special cases. A combinational path that goes straight from an input port to an output port, bypassing all flip-flops, cannot be timed by input and output delays alone, because those reference a clock. A direct max delay closes the gap. Asynchronous datapaths are another fit. When a signal crosses between unrelated clock domains through pure logic, the clock relationship is undefined, but you may still want to cap the raw propagation time so the signal does not take absurdly long. A max delay does this without pretending there is a synchronous relationship. This is a more nuanced use than it first appears. On an async crossing you often declare a false path so the tool stops doing a meaningless setup or hold check. But declaring the path false removes all limits, and sometimes you still care that the raw delay stays bounded, for example so a handshake completes in a reasonable time. The pattern is to combine the two: a false path to suppress the bogus synchronous check, and a max delay to keep the propagation under control. The max delay then acts as a sanity cap rather than a true timing requirement.
Be careful: a direct delay bound overrides the normal clock-based check on that path. If you bound a register-to-register path with set_max_delay , you replace its clock-derived limit. Use this only when you mean to, because it can mask or loosen the real requirement. There is one more practical detail about how the limit is applied. A clock-based setup check measures from the launch edge through the path to the capture edge, accounting for clock latency on both ends. A set_max_delay bound, by contrast, usually measures the pure pin-to-pin delay along the path, with no clock edges involved. This means the number you choose for a direct bound must already include any margin you want; there is no period or uncertainty doing that work for you. If you want 0.20 ns of safety margin on a 2.40 ns path, you set the limit to 2.20 ns yourself. This also affects how you reason about failures. When a clock-based path fails, you can think about borrowing time from the period, tightening uncertainty, or relaxing with a multicycle. When a directbounded path fails, your only levers are making the logic faster or raising the limit, because there is no clock structure to adjust. Direct bounds are simpler but also blunter; you own the number completely.
| Use case | Command | Note |
|---|---|---|
| In-to-out combinational | set_max_delay | No clock to reference |

clock to anchor it, such as a pure combinational input-to-output path, or an asynchronous crossing where the clock relationship is meaningless, or when you want an explicit limit different from what the clock would impose. set_max_delay 2.40 puts a hard 2.40 ns cap on the path.
hold-like race where new data overtakes old data. A floor like set_min_delay 0.30 forces the tool to keep the path at or above 0.30 ns, inserting delay if the natural path is faster.
Input and output delays reference a clock edge. A pure combinational path has no flip-flop and no clock at either end, so there is no edge to launch or capture against. A direct bound with
set_max_delay gives the tool the number it needs.
slack is 2.40 - 2.05 = +0.35 ns. The path passes. If the actual delay were 2.55 ns, slack would be 2.40
- 2.55 = -0.15 ns, a failure, and the tool would work to speed the path up. Q5. What is the risk of using set_max_delay on a register-to-register path? It overrides the clock- derived limit on that path. If the override is looser than the real clock requirement, you mask a genuine timing need. Use direct bounds on clocked paths only deliberately, knowing you are replacing the normal check. Q6. How do set_max_delay and set_min_delay work together? They define a window. Max sets the longest allowed time (setup-like), min sets the shortest (hold-like). A path with limits 0.30 ns and
2.40 ns and actual delays 0.42 ns and 2.05 ns sits safely inside, passing both checks.
Key Takeaways
- set_max_delay and set_min_delay bound a path directly, independent of any clock.
- They suit pure combinational paths, async crossings, and cases needing explicit control where no clock edge applies.
- Max delay feeds a setup-like check (too slow fails); min delay feeds a hold-like check (too fast fails).
- Together they form a window, e.g. 0.30 ns to 2.40 ns, that the real delay must fit inside.
- A direct bound overrides the clock-based check on a path, so use it on clocked paths only with care.
ChipBuddy
← Home
Comments
Leave a Reply