Writing Basic SDC Constraints
The previous article explained what STA checks. This one is about telling it what to check against.
Section 8.2 closed with a claim worth taking seriously: a large share of disappointing synthesis results trace back to a missing or wrong constraint, not to the RTL. This is where you learn to write the constraints that matter most, in SDC (Synopsys Design Constraints), the format nearly every synthesis and STA tool reads regardless of vendor.
create_clock: defining the clock period
The single most important constraint in any project. Without it the tool has no idea what “fast enough” means for your design and typically falls back to some generic, unconstrained optimisation that has nothing to do with your target:
create_clock -name clk -period 5.0 [get_ports clk]
This declares a clock named clk on the port clk with a period of 5.0 ns, a 200 MHz target. By default this implies a 50 percent duty cycle. If you need something else, a waveform argument gives the rise and fall times:
create_clock -name clk -period 5.0 -waveform {0 2.0} [get_ports clk]
This makes clk high for 2.0 ns of the 5.0 ns period, a 40 percent duty cycle, which matters for designs that use the two halves of the period differently (some DDR-style interfaces deliberately use an asymmetric clock).
set_input_delay and set_output_delay: the boundary
create_clock describes timing inside the design. Every real module also has inputs arriving from, and outputs going to, logic outside the boundary being constrained, and the tool needs to know how much of the period is already spent before a signal reaches your input pins, and how much margin your outputs must leave for whatever is downstream.
# This input already has up to 1.2 ns of delay before it reaches our port,
# relative to the clock edge, from logic in whatever module drives it
set_input_delay -clock clk 1.2 [get_ports data_in]
# This output must arrive with at least 1.5 ns of margin before
# the NEXT module's capturing flip-flop needs it stable
set_output_delay -clock clk 1.5 [get_ports data_out]
Without these, the tool assumes inputs arrive at the clock edge and outputs may settle right at the next one, an unrealistically generous assumption that gives the critical path far more slack than it will have once the module is integrated. This is the trap from the end of the previous article: a design reporting comfortable positive slack with no boundary constraints can turn into one with real negative slack the moment accurate constraints are added. The RTL never changed; the tool was simply never told the truth about the module’s operating context.
A complete minimal constraint set
# Clock definition: 200 MHz, 50% duty cycle
create_clock -name clk -period 5.0 [get_ports clk]
# Input timing: how much delay exists before signals reach us
set_input_delay -clock clk 1.0 [get_ports {data_in[*]}]
set_input_delay -clock clk 0.5 [get_ports enable]
# Output timing: how much margin downstream logic needs
set_output_delay -clock clk 1.0 [get_ports {data_out[*]}]
# Reset is typically NOT synchronous timing at all: exclude it from
# ordinary setup/hold analysis (it has its own separate treatment,
# per Section 7.5's discussion of async signals)
set_false_path -from [get_ports rst_n]
That last line deserves a note. set_false_path tells STA not to analyse this path with ordinary setup and hold checks at all. That is appropriate for an asynchronous reset, which is meant to be handled outside synchronous timing (the async-assert reasoning from Section 5.5), and dangerous on any path that does need real analysis. Marking a timing-sensitive path as false to make a report look clean, rather than because the path is exempt by design, is one of the more serious mistakes a team can make. It does not fix a timing problem; it hides it from the report that exists to catch it.
What happens with no constraints
An unconstrained or under-constrained run is not neutral, it is misleading. A tool with no target to optimise against makes choices (less aggressive cell selection, less effort on the critical path, no reason to suppress sharing) that happen to look clean purely because nothing was being checked against the real requirement.
A report showing zero timing violations tells you nothing on its own. The next question has to be “zero violations against what constraints?” A missing set_input_delay on a critical input, or a create_clock period set to a comfortable placeholder instead of the real target, produces exactly this false-clean report. Confirm the constraint file matches the specification before trusting a clean report. The constraints are not a formality; they are the definition of what “correct timing” means for this run.
Practice Exercise
A module is clocked at 250 MHz, its cmd_in input arrives 0.8 ns after the clock edge (from logic in the driving module), and its result_out output needs at least 1.0 ns of margin before the next module’s flip-flop. Write the complete SDC set.
Solution
# 250 MHz = 4.0 ns period
create_clock -name clk -period 4.0 [get_ports clk]
set_input_delay -clock clk 0.8 [get_ports cmd_in]
set_output_delay -clock clk 1.0 [get_ports result_out]
The period is $1 / 250\text{ MHz} = 4.0$ ns, the $f_{max} = 1/T$ relationship from the previous article in reverse. Here you specify the target period and the tools check whether the design’s critical path can meet it, rather than you computing the critical path by hand as in that article’s exercise.
SDC constraints are how you tell synthesis and STA what the design actually needs: create_clock sets the period every path is checked against, and the input and output delay constraints tell the tool the truth about the boundary rather than the generous default of everything arriving and departing at the edge. A timing report is only as meaningful as the constraints it was checked against. In the next article we look at how synthesis uses these constraints to make the area-versus-timing trades from Section 8.2 concrete.