Module 2: Logic Synthesis ยท 2.4 Synthesis Constraints

Writing a First SDC File

Section 2.3 was about what the library tells synthesis. Section 2.4 is about what you tell it.

Every synthesis run so far has quietly relied on CLOCK_PERIOD being 10.0 in alu8’s config. But CLOCK_PERIOD is a convenience OpenLane provides. Under the hood it is translated into a constraint file in the industry-standard SDC (Synopsys Design Constraints) format, the format nearly every synthesis and STA tool reads, open-source or commercial. This article writes one from scratch.

Why a constraint file has to exist

Recall the mapping cost function from Section 2.1: ABC picks coverings that minimise area, delay or a blend, depending on SYNTH_STRATEGY. But “minimise delay” relative to what target? Without a statement of “this design must run this fast”, the tool has no deadline to optimise against. It either defaults to some generic target unrelated to your requirement, or optimises purely for area with no timing check at all. SDC is how you tell the tool, precisely and in a standard machine-readable form, what correct timing means for this design.

A minimal SDC for alu8

alu8’s ports: clk, rst_n, a[7:0], b[7:0], op[2:0], result[7:0], zero, carry_out. A minimal but complete constraint set needs three kinds of statement.

  1. Define the clock.
create_clock -name clk -period 10.0 [get_ports clk]

This declares a clock named clk on the port clk with a period of 10.0 ns, a 100 MHz target:

$$ f = \frac{1}{T} = \frac{1}{10.0\ \text{ns}} = 100\ \text{MHz} $$

By default this implies a 50 percent duty cycle; the next article covers when and why to override it.

  1. Constrain the inputs.
set_input_delay -clock clk 2.0 [get_ports {a[*]}]
set_input_delay -clock clk 2.0 [get_ports {b[*]}]
set_input_delay -clock clk 2.0 [get_ports {op[*]}]

This says: by the time a, b and op reach alu8’s inputs, 2.0 ns of the period has been spent by whatever upstream logic drives them. alu8 does not get the full 10.0 ns on paths starting at these ports. The next article makes the arithmetic precise.

  1. Constrain the outputs.
set_output_delay -clock clk 1.5 [get_ports {result[*]}]
set_output_delay -clock clk 1.5 [get_ports zero]
set_output_delay -clock clk 1.5 [get_ports carry_out]

This says: whatever consumes result, zero and carry_out downstream needs 1.5 ns of margin after this design’s clock edge before it needs them stable. Since alu8’s outputs are registered (the clocked always block in Section 2.2’s design), these delays constrain register-to-port paths, and the input delays constrain port-to-register paths, not a pure combinational pass-through.

Putting it together

# alu8.sdc: minimal, complete constraint set

create_clock -name clk -period 10.0 [get_ports clk]

set_input_delay  -clock clk 2.0 [get_ports {a[*]}]
set_input_delay  -clock clk 2.0 [get_ports {b[*]}]
set_input_delay  -clock clk 2.0 [get_ports {op[*]}]

set_output_delay -clock clk 1.5 [get_ports {result[*]}]
set_output_delay -clock clk 1.5 [get_ports zero]
set_output_delay -clock clk 1.5 [get_ports carry_out]

# rst_n is asynchronous: exclude it from setup/hold analysis
set_false_path -from [get_ports rst_n]

Every synthesis and timing report from here in the module is checked against these lines. Two mistakes to avoid: putting an input or output delay on the clock port itself, and forgetting rst_n. An asynchronous reset needs a set_false_path (or equivalent handling) rather than an ordinary delay constraint; left unconstrained it produces an “unconstrained path” warning that is easy to mistake for a real problem.

Practice Exercise

alu8 is being integrated into a system at 200 MHz, with a and b arriving 1.2 ns after the clock edge and result needing 1.0 ns of margin before the next stage’s edge. op and the other outputs keep the constraints above. Write the create_clock and delay lines for the new target.

Solution
# 200 MHz = 5.0 ns period
create_clock -name clk -period 5.0 [get_ports clk]

set_input_delay  -clock clk 1.2 [get_ports {a[*]}]
set_input_delay  -clock clk 1.2 [get_ports {b[*]}]
set_input_delay  -clock clk 2.0 [get_ports {op[*]}]

set_output_delay -clock clk 1.0 [get_ports {result[*]}]
set_output_delay -clock clk 1.5 [get_ports zero]
set_output_delay -clock clk 1.5 [get_ports carry_out]

The period is $1 / 200\text{ MHz} = 5.0$ ns. Only the constraints the prompt named change (a, b, result and the period); op, zero and carry_out keep their values. Changing one part of a constraint set does not silently change the rest.

You now have a constraint set that runs. In the next article we look at what create_clock and the delay constraints model: duty cycle, and why the I/O delays describe the real world outside the chip rather than arbitrary padding.

โ† Drive Strength and Cell Variants create_clock and I/O Delay Budgets โ†’