A Loom engine for SDC timing constraints: it checks them, writes them, and compares two sets of them.
A timer computes timing on whatever constraints it is given. A constraint that is wrong does not
produce a wrong-looking report; it produces a clean one. An output with no
set_output_delay is a path nobody checks. A clock on a port that drives nothing leaves every
register untimed while the SDC still looks populated. A set_false_path with a typo names
nothing and quietly does nothing. vyges-sdc check looks for exactly these.
Constraints also change: an edit, a new delivery, a netlist flow's copy of the RTL one. A text diff
of two constraint files shows every reordering and reformatting and hides what matters.
vyges-sdc diff says which constraints changed and whether each change loosens or tightens what
is checked.
Some findings are intended. A waiver records that decision as an open Tcl file of
waive_finding commands, one per finding, keyed by an id that survives edits elsewhere in the
SDC. check --waivers applies them and reports any that went stale or expired, and waivers export writes them as another tool's waiver commands, by a template you supply.
vyges physical sdc check --sdc design.sdc [--netlist design.v --lib cells.lib [--lib ...]]
[--top NAME] [--keep MODULE ...] [--metadata vyges-metadata.json]
[--define NAME=VALUE ...] [--waivers waivers.tcl]
[--write-waivers new-waivers.tcl] [--fail-on-warning]
[--rtl [--ranges ranges.json]] [--json] [-q|--quiet ...] [-v|--verbose ...]
vyges physical sdc write --sdc design.sdc [-o|--out normalised.sdc]
[--netlist design.v --lib cells.lib [--lib ...]]
[--top NAME] [--keep MODULE ...] [--define NAME=VALUE ...]
[--design NAME] [--partial] [--rtl [--ranges ranges.json]] [--json]
[-q|--quiet ...] [-v|--verbose ...]
vyges physical sdc diff --sdc old.sdc --against new.sdc [--map names.json]
[--netlist design.v --lib cells.lib [--lib ...]]
[--top NAME] [--keep MODULE ...] [--define NAME=VALUE ...]
[--rtl [--ranges ranges.json]] [--json] [-q|--quiet ...] [-v|--verbose ...]
vyges physical sdc map --sdc rtl.sdc --netlist elab.v --lib cells.lib [--lib ...]
--map names.json [--ranges ranges.json] [--revised impl.v]
[--top NAME] [--keep MODULE ...]
[--define NAME=VALUE ...] [--json] [-q|--quiet ...] [-v|--verbose ...]
vyges physical sdc waivers export --waivers waivers.tcl --template target.txt [-o out.tcl] [--json]
vyges physical sdc --describe | --version | --help
vyges physical sdc --bug-report | --feature-request | --sponsor | --starvyges-sdc --help describes each option; a test fails when this block and --help name
different options.
check tests the constraints against the netlist, with the Liberty to say which cells are
registers:
- Completeness: inputs and outputs with no I/O delay, registers that no clock reaches, and endpoint coverage (the share of timing endpoints that are constrained at all), reported on every run, clean ones included.
- Consistency: duplicate clock names, clocks on ports the design does not have, and
exceptions (
set_false_path,set_multicycle_path) that name no object or no structural path. - Overlapping exceptions: an exception another one outranks on every path it names (shadowed), two that disagree at the same priority (conflict), and one that adds nothing (redundant), with the winner decided as the reference timer decides it. Needs no netlist.
- Clock groups: a generated clock left out of the groups its master is in, two clocks of
one family declared asynchronous, and words of
set_clock_groupsthe reference timer ignores (an option it lacks, a-groupname that matches no clock). Needs no netlist. - Declared clock domains: given
--metadata, the IP'sclock_domainsfromvyges-metadata.jsonare checked against the SDC's clocks. A three-clock IP with onecreate_clockpasses timing and times only part of the design; this catches it.
Every check also reports each command that was read but not applied: one the model does not carry, one it refused, or one whose object query it could not evaluate. Such a command is an error when it affects timing (a constraint the timer never sees is a hole in the constraints) and information when it does not (a synthesis directive).
It reports, too, each command that is kept (read and written back) but applied by no checker
or timer here, with its count: set_clock_gating_check, the wire-load and operating-condition
commands, set_sense, set_clock_sense, for example (the coverage table below marks them Kept). That is a warning
when applying it could move a slack, so the report is not called clean, and information when it
could not (the area and power limits set_max_area, set_max_dynamic_power,
set_max_leakage_power). It is not an error, since the command was read as the reference timer
reads it. The JSON result counts them in kept_not_applied.
It reports overlapping exceptions, with the winner on the shared paths decided as the
reference timer decides it: the type first (a false path, then a max or min delay, a multicycle, a
path margin, a path group), then the points (-from pins, -to pins, -through, -from clocks,
-to clocks), with a multicycle's -setup or -hold counting in the check it names.
| code | severity | meaning |
|---|---|---|
exception-shadowed |
warning | Another exception covers every path this one names and outranks it, so it never applies |
exception-conflict |
warning | Another covers the same paths at the same priority with a different value: the one read later replaces the earlier, or the tighter value wins, or the reference has no defined choice |
exception-redundant |
information | Another covers every path it names and says the same, so it adds nothing |
exception-matches-nothing |
warning | With --netlist, or for an empty {}: a -from, -through or -to whose objects resolve to nothing (a -quiet query or a -filter that matches no object). The reference timer warns and makes no exception from the whole command, so the constraint does nothing; it is not read as applying to every path |
exception-overlap-not-checked |
information | Exceptions not checked for overlap: a file with -reset_path, or objects named in a way the check cannot classify (a bare name list), with the first one named |
Two rules that are easy to get wrong:
- Two multicycles, or two max delays, with the same priority and the same kinds of points: the later one in the file replaces the earlier one. It does not matter which is tighter.
- A
-setupmulticycle also moves the hold check. A-setupfalse path or aset_max_delayover it leaves that effect, so it is not reported.
An overlap is reported only when it can be shown from the file: each point of the covering exception names, with the same query, every object the covered one names (a pattern may match them). A clock that launches a pin, or a cell that owns a pin, is not resolved, so the check can miss an overlap but does not report a false one.
It reports clock groups that do not say what they appear to, with the groups read as the
reference timer reads them: a group holds exactly the clocks its -group names (each word a
pattern over the clocks defined before the command), and a command with one group separates its
clocks from every other clock. With --netlist, a generated clock's master is found as the
reference finds it, through the design from its -source, and its waveform derived from that
master, so a divider whose -source is a pin with no clock on it is timed at its own period.
| code | severity | meaning |
|---|---|---|
clock-generated-ungrouped |
warning | A generated clock is in no group of a command that groups its master: it is timed against the other groups, or, under a one-group command, cut from its own master |
clock-related-declared-async |
warning | Two clocks of one family (one generated from the other, or both from one clock) are in different -asynchronous groups: their paths go untimed, which is right only if every crossing between them is synchronized |
clock-relations-not-checked |
information | Generated clocks whose master is not known: without --netlist, those with no -master_clock and no clock on the -source; with it, those the search cannot decide as the reference would (two clocks one step back, a disabled arc), with the first one named |
clock-generated-no-master |
warning | With --netlist: no clock reaches a generated clock's -source through the design (wires, combinational arcs, register clock-to-Q), so the reference finds no master and derives no waveform |
clock-divider-not-register |
warning | With --netlist: a dividing generated clock is defined at a pin that combinational logic drives; a divided clock taken from logic can glitch |
sdc-word-ignored |
warning | A word the reference timer warns about and ignores while applying the command: an option set_clock_groups does not have (-include_generated_clocks), a stray word, or a -group name that matches no clock defined before it |
It names, at information level and with a count, each command or option the file uses that the
reference timer reads but no SDC version declares (get_clocks -filter, -reset_path, set_pvt,
the unset_* commands; the last table below lists the options). The file is read as the
reference timer reads it; another SDC reader may refuse it.
Waivers are a Tcl file of waive_finding commands, one per accepted finding, meant to sit
with the other Tcl files a flow sources:
waive_finding -id exception-shadowed:3f2a9c1d07be -code exception-shadowed \
-objects {{set_multicycle_path 3 -from [get_pins r1/CK]} {set_false_path -from [get_pins r1/CK]}} \
-reason {the false path is intended; the multicycle is left for the next netlist} \
-owner {jdoe} -expires 2027-06-30--write-waivers FILEwrites one such command for every error and warning still active, with the finding above it as a comment and an empty-reasonto fill in. It never overwrites a file.--waivers FILEapplies them. A waived finding is listed underwaived, with its reason, and counts as neither an error nor a warning.- The
-idis the finding's identity: its code and what it is about. It does not depend on line numbers or on the order of the file, so it survives edits elsewhere, and it changes when what the finding is about changes. - A waiver that waives nothing is a warning of its own:
waiver-stale(it matches no finding),waiver-no-reason,waiver-expired(-expiresis aYYYY-MM-DDdate, in UTC, and the waiver applies through that day),waiver-duplicateandwaiver-malformed. - Another tool can
sourcethe file after definingwaive_finding, or read it as data: it holds no other Tcl. Onlywaive_findingis read.
Without --netlist, check runs what the file alone supports: the not-applied report, clock
periods and duplicate names, overlapping exceptions, plus one note that the design checks did not run.
Hierarchical netlists are flattened before checking, as the timer reads them, so a constraint
on u_core/u_alu/... finds its object. --top NAME names the top module when the file has more
than one candidate (otherwise the read is refused, listing them), and --keep MODULE keeps a
module as a block.
--define NAME=VALUE (repeatable) sets a Tcl variable before the file is read. Synthesis flows
often define names such as a driving cell or a period factor in a setup script that the constraint
file only uses.
The exit code is 1 when there is any error. Warnings are reported and leave it at 0.
Checking at RTL. check, write and diff take --rtl when the netlist is the elaborated
RTL (vyges physical syn synthesize): each register is read by its RTL name, the name on its
stored-value output net (its instance u12 becomes cnt[0]), and everything else is read as
written. When that net is itself a port (the register drives the port directly), the register takes
the port's name; map names it the same way. The constraint file written against the RTL is then checked against the RTL, before any
implementation exists, with the same checks the gate-level netlist gets; the gate-level path is
unchanged. Without --rtl, a query that reads the design, such as get_pins -of_objects [get_cells {cnt[0]}] or get_cells -filter {ref_name =~ DFF*} {u_core.*}, finds no cell of that name, and
the command is reported as matching nothing and left out of what write writes. A register that cannot take its name keeps its instance name, with an SDC-RTL-VIEW
warning saying why: name-taken (the name would not be unique) or no-stored-value-output (its
stored-value output is not connected). map reads the elaborated names itself and does not take
--rtl.
Under --rtl the file's names are read the way map reads them, by the same code. An RTL form
of a register name is matched by its alias (cnt_reg[0] is cnt[0], u_core/r is u_core.r).
With --ranges, a declared bit index is read as the elaborated one (cnt[9] of [9:8] is
cnt[1]), registers and ports alike. Each name read this way is reported with the same events
map uses (SDC-MAP-BIT, SDC-MAP-ALIAS). An alias that two registers could both mean, or an
index outside its declared range, is reported (SDC-MAP-UNKNOWN) and read as written: it is never
moved to a register or bit that exists. A clock name and a -regexp pattern are not RTL names and
are read as written. --ranges without --rtl is refused.
vyges physical sdc check --sdc rtl.sdc --netlist elab.v --lib cells.lib --rtl --ranges ranges.jsonwrite reads the SDC into the shared constraint model and writes it back out in canonical form:
- in the order a timer writes its own constraint database;
- with each value in the shortest form that reads back to the same number;
- in the units the file declared.
A netlist (and Liberty) lets object queries that need the design (-filter, -of_objects,
-regexp) be evaluated rather than refused.
Nothing is dropped silently. If any command would be left out (one the model does not carry,
or one it refused or could not evaluate), write writes no file. It exits 1 and lists every
such command. With --partial it writes the rest, and each command it left out appears in the
file as a # not written: line. The JSON result says complete: false either way.
{ "command": "write", "status": "refused", "complete": false,
"not_written": [ "not modelled: set_assigned_delay" ] }Malformed constraints are refused when the file is read rather than guessed at. For example, a
-period given with a unit (20ns, "1 MHz") is an error, because SDC periods are plain
numbers in the file's time unit. A design-rule limit (set_max_capacitance, set_max_fanout,
set_max_transition) without its value or its objects, with an option it does not have, or with a
value that is not a positive number is refused with a message, and check reports it as an
error: set_max_capacitance 0.2 (no objects) and set_max_fanout -clock_path 8 [get_clocks c]
(set_max_fanout has no option) are both errors. set_max_capacitance -clock_path 0.1 [get_clocks c] is SDC 1.8 in newer releases of the vendor's SDC parser (†): it is read, kept as written and
reported as kept but not applied.
diff compares two SDC files as constraints, not as text: an edit against the next, one
delivery against the next, or an RTL constraint file against the netlist one. It reports each
constraint that was added, removed or changed, and what the change does to what is checked:
| Direction | Means | Examples |
|---|---|---|
| loosens | fewer or easier checks | a longer period; a new false path; a larger multicycle; a smaller -max I/O delay; a removed clock or I/O delay |
| tightens | more or harder checks | a shorter period; a larger uncertainty; a removed false path; a larger load |
| neutral | nothing checked changes | a changed -comment |
| unknown | no rule decides it | one I/O delay for both -min and -max (tighter setup, looser hold); a waveform; a latency; an unmodelled command |
A gate that must not let constraints loosen unnoticed treats unknown as it treats loosens.
What is a difference, and what is not. Each constraint is identified by what it is ON: its
command, its flags, the options that name what it applies to (-clock, -from, -to,
-through, -name, …) and its objects. A clock is identified by its name alone, so a new
period or source is a change to that clock, not a new one. What it SAYS (the numbers, the other
options, -comment) is compared field by field. These are not differences:
- the order of commands, or of names inside a list;
- one command on several ports, against one command per port;
- a value a later command in the same file overrides, as SDC's own merge does; the overridden
command is listed as
superseded.
Objects are compared as written. With --netlist, both files are read against the design, so
queries are compared by the objects they resolve to. --map renames the first file's objects
before comparing: a JSON object of old name to new name, matched against a whole name or the
instance part of a pin, longest first. That is how an RTL constraint file is compared with a
netlist one. vyges physical lec map --write-map names.json writes such a map, each register pair
proved to hold the same value rather than matched by name.
vyges physical sdc diff --sdc rtl.sdc --against netlist.sdc --map names.jsonchanged loosens create_clock -name io_aclk -period 5000 -> 6000
changed tightens set_clock_uncertainty -setup get_clocks {all_clocks} value 250 -> 300
removed tightens set_false_path -from get_ports -quiet {io_aresetn} set_false_path -from [get_ports -quiet io_aresetn]
Commands the model does not carry are compared by their text. An if block that needs the
design to evaluate, or a command the reader reads past, is compared as the reader read it: its
text, with whitespace collapsed, its variables substituted, and the values of the variables it
names. The same text on both sides is the same constraint; a text on one side only is a change of
direction unknown.
What cannot be compared is said. A rejected command, or any unmodelled command when --map is
given (a raw text cannot be renamed faithfully), is listed under not_compared, and the two files
are then never reported as equivalent, even when no change was found. Values are compared in the
units each file declared; when the two declare different set_units, units_differ says so.
{ "command": "diff", "status": "finding", "equivalent": false,
"by_direction": { "loosens": 0, "tightens": 1, "neutral": 0, "unknown": 0 },
"changes": [ { "kind": "removed", "direction": "tightens", "command": "set_false_path",
"key": "set_false_path -from get_ports -quiet {io_aresetn}", "fields": [],
"a": "set_false_path -from [get_ports -quiet io_aresetn]", "b": null } ],
"not_compared": { "sdc": [], "against": [] },
"superseded": { "sdc": [], "against": [] }, "units_differ": false }Exit 0 means equivalent; exit 1 means the files differ or something was not compared.
map carries an RTL constraint file to an implementation netlist. The file is resolved against the
ELABORATED design, where each name it uses still exists: a register is named by the RTL name on its
stored-value output net (the elaborated cell itself is u<n>), a pin as <register>/<pin>. Each
object is then carried:
- a port, or a clock, as itself (with
--revised, the port must exist there); - a register, or a register's pin, through
--map, the register mapvyges physical lec map --write-map names.jsonwrites, each pair proved to hold the same value; - a net or a combinational cell is not carried: no proved match exists for one.
Nothing is carried by a name heuristic, and nothing is passed over. A pattern that names nothing in the elaborated design (a misspelled register), a register with no proved pair, a list that needs the design to evaluate, and a command whose objects could not be read are each reported, with why.
vyges physical sdc map --sdc rtl.sdc --netlist elab.v --lib cells.lib --map names.json --revised impl.vcarried port clk -> clk
carried register s1 -> _10_
carried register_pin s2/D -> _11_/D
unknown get_cells s9 (set_false_path -to [get_cells {s9}])
not carried net nx (combinational)
Bit indices. Elaboration numbers every vector's bits from 0 at the right bound of its
declaration, ports included: logic [11:8] r becomes r[0]..r[3], and logic [8:11] s makes
s[11] into s[0]. With --ranges (the declared ranges, as vyges physical syn sv-ranges
writes them), an RTL index is translated to the elaborated one (r[8] to r[0], q[8] of a port
declared [11:8] to q[0], a register pin r[9]/D to r[1]/D), and each translation is listed
(bit, SDC-MAP-BIT, bits in the JSON). An index outside the declared range is reported as such
and never moved to a bit that exists.
bit get_cells r[9] is elaborated r[1] (declared [11:8])
out of range get_cells r[12] (declared [11:8]; set_false_path -to [get_cells {r[12]}])
RTL name forms. A constraint file written against the RTL often names a register as a synthesis
tool would: u_core/state_reg[2], or with its output pin, u_core/state_reg[2]/Q. A pattern that
names nothing as written is tried again with these forms read as aliases of the elaborated name
u_core.state[2]: / as the hierarchy separator (separator), the register name with or without
_reg (reg-suffix), and a trailing register output pin on a cell query read as the register
(output-pin; get_pins keeps the pin, as the register's pin). Each match is listed with the rules
it used (alias, SDC-MAP-ALIAS, alias in the JSON). A form that two elaborated registers could
both mean is reported with its candidates (ambiguous, in SDC-MAP-UNKNOWN), never guessed.
alias get_cells u_core/state_reg[2]/Q matched by output-pin, separator, reg-suffix
ambiguous get_cells u/t[0] (matches u.t[0] u.t_reg[0]; not guessed; set_false_path -to [get_cells {u/t[0]}])
The reasons are register-not-proved (no map entry), mapped-cell-not-in-revised,
port-not-in-revised and combinational. --json lists every object with its members, each with
kind, carried and reason, and complete. Exit 0 means every object was carried; exit 1 means
something was not.
waivers export writes a waiver file as the Tcl waiver commands another timing, constraint or
lint tool sources. A template says how: the command line written per waiver, how each object
is written, and which of that tool's rule ids each of our finding codes stands for.
# one line per waiver; a part in [[...]] is written only when its values are present
command my_waive -status {Waived} -rule {@rule@} -match {@objects@} -comment {@reason@}[[ -owner {@owner@}]]
object name=~"@object@"
join \s||\s
rule exception-shadowed OVERLAP_SHADOW
rule exception-conflict OVERLAP_CONFLICT OVERLAP_VALUE
- Placeholders in
command:@rule@,@objects@,@reason@,@owner@,@expires@,@id@,@code@. Inobject:@object@, or@object_re@with regular-expression metacharacters escaped (for a target that matches by regexp).joingoes between objects (\s,\t,\nfor a blank, a tab, a newline). rule * …maps every code not mapped otherwise, and@code@in it is our own code.- A waiver whose code has no mapping is not written. It is listed, and the exit code is 1: a guessed rule id would be a waiver the other tool silently ignores. A waiver with no reason, or a command that is not a valid waiver, is listed the same way.
- An unknown directive or placeholder in the template is an error, so a typo cannot reach the other tool's file. A value whose braces do not balance is escaped, so every command still parses as Tcl.
-onever overwrites a file. Without it the result goes to stdout (with--json, the envelope'stext).
This template writes our own format back, which is how the export is tested:
command waive_finding -id @id@ -code @code@ -objects {@objects@} -reason {@reason@}[[ -owner {@owner@}]][[ -expires @expires@]]
object {@object@}
rule * @code@
These are the constraints write writes back, read with the semantics of the reference timer.
The list is checked against what the engine actually writes, so it cannot claim more than the
code does.
| Group | Commands |
|---|---|
| Clocks | create_clock, create_generated_clock, set_clock_uncertainty, set_clock_latency, set_clock_transition, set_propagated_clock, set_clock_groups |
| Inputs and outputs | set_input_delay, set_output_delay, set_input_transition, set_load, set_driving_cell |
| Timing exceptions | set_false_path, set_multicycle_path, set_max_delay, set_min_delay |
| Constant logic | set_case_analysis, set_logic_zero, set_logic_one, set_logic_dc |
| Derating and units | set_timing_derate, set_units |
| Design rules | set_max_transition, set_max_fanout, set_max_capacitance |
| Arcs and senses | set_disable_timing, unset_disable_timing, set_sense, set_clock_sense, set_disable_clock_gating_check, unset_disable_clock_gating_check |
Objects. get_ports, get_pins, get_cells, get_nets and get_clocks with their
patterns, all_inputs, all_outputs, all_clocks, all_registers, and Tcl lists of these
([list …], [concat …]). With a netlist, queries that select by property or connectivity
(-filter, -of_objects, -regexp) are evaluated against the design. A cell -filter reads
name, full_name, ref_name, is_hierarchical, and from the cell's Liberty is_buffer,
is_inverter, is_clock_gate, is_macro and is_memory (as the reference timer reads them: a
clock buffer is a buffer, a cell with a memory group is a memory, a pad or level shifter is
neither a buffer nor an inverter; == true, == 1 and a bare name are the same test).
Extension: a cell -filter also reads is_sequential, which the reference timer does not
have: true when the cell's Liberty holds state as a register does (an ff or latch group or
bank, a statetable, or a clock pin on a cell that is not a clock gate). An integrated clock
gate built on a statetable therefore counts as sequential; is_sequential && !is_clock_gate
leaves it out. A property the reference
timer does not have for those objects (is_register on a cell, is_clock on a port) is its
error: the query fails and it stops reading the file at that command, so the read stops here too
(exit 2), as at a command it does not know. So does a filter it cannot read: a character no
filter token starts with (ref_name ~= X, a quoted "X"), an extra ) or an unclosed (, an
operator without its operands (a &&, !), an empty filter, or two terms with no operator
between them. The stop happens wherever Tcl runs the query: in a command's words (before the
command checks its options), a quoted word, puts, a foreach list, a while test, an if or
elseif test that is reached, the part of a test before its first &&, || or ?, a braced
expr, a taken branch, a called proc. A query in a branch not taken, a proc never called or a
braced word is not run, so it does not stop the read. A filter applied to no object is no error,
malformed or not. A property
it has that is not evaluated here (timing, liberty_cell) is refused, and that command is not
applied. Every input or output but
some is read in both common spellings:
[lsearch -inline -all -not -exact [all_inputs] [get_ports clk]] and
[remove_from_collection [all_inputs] clk]. Options no SDC version declares, such as
get_pins -leaf and get_nets -segments, are refused (the command is not applied), as the
reference timer refuses them. With the libraries and a netlist, [load_of lib/cell/pin] is that
library pin's capacitance; without them its command is not applied.
SDC options the reference timer lacks are read, never refused: -level_sensitive on
set_input_delay / set_output_delay, -min / -max on set_timing_derate, -clock /
-clock_fall on set_driving_cell, the all_inputs, all_outputs and all_registers options,
-unit on the power limits, -object_list on set_operating_conditions and -group_name on
set_wire_load_selection_group. Each is written back, and check reports it as kept but not
applied: a command whose meaning it changes (a clock-scoped drive, an input list selected by
clock) is kept as written rather than applied as if the option were not there. get_ports -hierarchical is get_ports (a port is at the top level), and set_load -substract_pin_load is
-subtract_pin_load, and set_false_path -rise_throough / -fall_throough, the public
parser's own spelling, are -rise_through / -fall_through. The table at the end of the command
coverage lists them.
SDC 1.8 in newer vendor releases. Vendor SDC 1.8 declares more than the public release of
the vendor's SDC parser, and how much more varies by release (†): -filter and -quiet on the
object and library queries, get_pins -of_objects, set_load -rise / -fall / -internal /
-allow_negative_cap, set_max_transition -data_path / -delay_penalty (a flag, taking no
value) / -force, set_max_capacitance -clock_path / -data_path / -rise / -fall /
-force / -sms_scenarios, the collection commands
(add_to_collection, append_to_collection, remove_from_collection, filter_collection) and
all_fanin / all_fanout. They are SDC 1.8 here too: read, never refused, and not named as
reference-timer extensions. An option the reference timer has is applied as it applies it; the
others are kept with their command and reported as kept but not applied. Every option any SDC
version declares, public or vendor, is read by a test that fails on a refusal.
Tcl. The subset constraint files use: set and variables ($name, ${name},
$::env(NAME)), if/elseif/else, foreach over a list, switch, expr, proc (each call
runs its body with its parameters bound and its own local variables; return ends it, and a proc
called inside […] stands for the value it returns), global, upvar (the caller's variable,
written back when the proc returns), dict (create, set, get, exists, keys, values,
size, merge, unset, lappend, append, incr and for, in insertion order), source
(read in place, from the working directory and then from the reading file's directory; a return
at a file's top level ends that file), puts, and abbreviated command and option names, expanded
as the reference expands them. Nothing is substituted inside braces, as in Tcl. Reading a variable
that is not defined is Tcl's error and stops the read there (exit 2), as it stops the reference
timer: can't read "X": no such variable. A flow's variables are given with --define. The singular
query spellings (get_port, get_cell, …) are the plural queries.
Files written for another tool. Constraint files that DFT insertion and signoff tools write
test which tool is reading them. $synopsys_program_name and [get_app_var synopsys_program_name] read as a signoff timer's name unless the flow defines the variable
(info exists sees only what is defined). The signoff timer's take-back commands are read as the
reference's: remove_input_delay, remove_output_delay, remove_propagated_clock,
remove_clock_uncertainty, remove_clock_latency, remove_clock_transition,
remove_case_analysis, remove_clock_groups, remove_disable_timing, remove_data_check,
remove_clock, remove_generated_clock, reset_path and reset_timing_derate. Without
-clock, remove_input_delay and remove_output_delay take back every delay on the objects,
whatever its clock, as the signoff timer does.
Synthesis-tool collections. append_to_collection and add_to_collection build object lists,
and get_object_name gives their names. A foreach_in_collection loop whose body hands each
object to constraints as their object is those constraints on the whole collection.
get_designs, filter_collection, get_attribute (also abbreviated, get_attr),
sizeof_collection, index_collection, sort_collection, all_fanin, all_fanout, and a
foreach_in_collection loop that needs its objects one by one are recognised: the command using
one is reported as not applied, and the constraints after it are still read.
Synthesis directives are recognised and read past: set_size_only, set_critical_range,
set_dont_touch_network, set_optimize_design, set_scan_configuration, set_min_porosity,
set_clock_gating_enable, set_clock_gating_style, set_clock_gating_verification and
set_timing_enable_verification. They steer optimisation and change no timing result, so the
constraints after them are applied. write lists each one as not written, since the file it
writes is for timing; --partial writes the rest.
Everything else the reference reads is read too. Constraints no checker or timer here applies
yet (set_min_pulse_width, set_max_time_borrow, the wire-load and
operating-condition commands, for example) are kept and written back as they were read, and
check reports each one. A command
the reference runs but that is no constraint (a tool setting such as set_assigned_delay) is
reported by name: check reports it, and write lists it as not written. A command that is not SDC or Tcl at all stops the read with an error that names it and
the line it is on (exit 2). The reference timer stops there too, and a constraint file read only
partly would otherwise be timed as if it were whole.
Every command of the SDC command set, with the SDC version that introduced it and what this
engine does with it. The version is the first SDC version whose declaration file, in the
reference SDC parser Synopsys published with the standard (SDC 1.1 to 2.0), declares the command:
the singular query spellings (get_cell, get_port, …) are SDC 1.5, though their plurals are
1.1. ‡ 2.1, per the reference timer's documentation; no public SDC 2.1 document. † Declared in
that version by the vendor's releases of the parser, and by none of the public release's files:
releases differ, and newer ones declare more in SDC 1.8 (see "SDC 1.8 in newer vendor releases"
above); a command or option marked † is SDC here. set_current_design (1.1 †) is read as
current_design; with a netlist, check warns when either names a design other than the
netlist's top (current-design-not-top), and the constraints are applied to the top.
The public release these versions are dated from is the copy distributed in the OpenTimer
project's ot/sdc/ directory (commit cd0565f), whose declaration files are:
| File | SHA-256 |
|---|---|
sdc1.1.tcl |
541d1d685eb1890bdc52fe00b2b6b79c2c39c5ea2405dfa43f6259b11c05e6ae |
sdc1.2.tcl |
c1aa2d55062b3207b3ad38fe20adb3beb08584c5074e156231cb62095f5449df |
sdc1.3.tcl |
a67592ac8c7a505d537a16b3a320d7f27a8848e0f60e4d693f12471a898702b9 |
sdc1.4.tcl |
11a6635a64648af715fd895faa058ce702959f26057b282220f3d1b9a8aec0d0 |
sdc1.5.tcl |
31905d514e2055b8ebea1c48abd8100966fed154879ac1c0e3e12cb967242e49 |
sdc1.6.tcl |
8ff3ed2add57486393da8145fc7ba06d0bc80b0b3d901ab114e64309feb9dc09 |
sdc1.7.tcl |
7d5443e13493a84dda9a7d46014ebac676b64ea315f58e6f852dda0c5e46154c |
sdc1.8.tcl |
b5944629e9610a1123f3a7329d911a8bb53223370dd12405780502ae72422056 |
sdc1.9.tcl |
5f175e7416d4ee0425e747e008bc4b723be9c383c262cd686f943a600c77abf5 |
sdc2.0.tcl |
6f0aaf1a4d4125b131c4965928d21994323ddf0bce5ea85470d3754005520c91 |
set_clock_jitter and set_disable_clock_gating_check are declared by no version the
parser covers, and are listed outside the set. The tables are generated from the command registry in loom
(vyges_loom::sdc::registry), whose own test reads one probe line per command and fails if a
status is not what the reader does. cargo run --example coverage_table prints them, and a test
in this repository fails when the README's copy differs. "Applied by" names the consumers'
code: change it with the consumer.
- Applied: read into the constraint model, written back, and applied by the tool named under
"Applied by":
check(this engine's check),sta(thevyges-tools/statimer),sta-si(thevyges-tools/sta-sitimer),rsz(thevyges-tools/rszdesign repair, for the design-rule limits), orreader(evaluated while the file is read: object queries, units, the current design and instance, and take-back commands; what it changes is applied where the command it changes is). - Kept: read as the reference timer reads it and written back, but applied by no checker or
timer here.
checkreports each one it reads, with its count. - Tcl: run as Tcl runs it.
- Read past: a synthesis-only command the reference timer does not run (
set_dont_touch,set_min_porosity; outside SDC,set_size_onlyand the like). It is skipped, reported as not applied at information level, andwritelists it as not written (--partialwrites the rest). One rule covers the area and power directives: one the reference timer reads is Kept (written back, reported at information level), one it does not is Read past. - Not applied: recognised but not evaluated (a fan-in query, a collection filter without the
design): the command that uses it is reported as not applied, as an error in
checkwhen it can move a slack, and the read goes on. - Unhandled: not read; the file stops there (exit 2), as it does in the reference timer.
SDC 1.1 to 2.1: 57 applied, 25 kept, 4 Tcl, 2 read past, 4 not applied, 0 unhandled, of 92 commands.
| Command | SDC | Status | Applied by |
|---|---|---|---|
add_to_collection |
1.8 † | Applied | reader |
all_clocks |
1.1 | Applied | reader |
all_fanin |
1.8 † | Not applied | — |
all_fanout |
1.8 † | Not applied | — |
all_inputs |
1.1 | Applied | reader |
all_outputs |
1.1 | Applied | reader |
all_registers |
1.7 | Applied | reader |
append_to_collection |
1.8 † | Applied | reader |
create_clock |
1.1 | Applied | check, sta, sta-si |
create_generated_clock |
1.3 | Applied | check, sta, sta-si |
create_voltage_area |
1.6 | Kept | — |
current_design |
1.1 | Applied | reader |
current_instance |
1.1 | Applied (-instance or the positional; an unqualified name under it, and -hierarchical under it, are refused) |
reader |
expr |
Tcl | Tcl | — |
filter_collection |
1.8 † | Not applied | — |
get_cell |
1.5 | Applied (spelling of get_cells) |
reader |
get_cells |
1.1 | Applied | reader |
get_clock |
1.5 | Applied (spelling of get_clocks) |
reader |
get_clocks |
1.1 | Applied | reader |
get_designs |
1.5 † | Not applied | — |
get_lib_cell |
1.5 | Applied (spelling of get_lib_cells) |
reader |
get_lib_cells |
1.1 | Applied | reader |
get_lib_pin |
1.5 | Applied (spelling of get_lib_pins) |
reader |
get_lib_pins |
1.1 | Applied | reader |
get_libs |
1.1 | Applied | reader |
get_net |
1.5 | Applied (spelling of get_nets) |
reader |
get_nets |
1.1 | Applied | reader |
get_pin |
1.5 | Applied (spelling of get_pins) |
reader |
get_pins |
1.1 | Applied | reader |
get_port |
1.5 | Applied (spelling of get_ports) |
reader |
get_ports |
1.1 | Applied | reader |
group_path |
1.7 | Applied (sta: -name groups, their path ends reported; no slack moves with it) |
sta |
if |
Tcl | Tcl (a condition the file alone decides (info exists, a variable); one on a query's result needs the design and is listed as not applied) |
— |
list |
Tcl | Tcl | — |
remove_from_collection |
1.8 † | Applied (only as every input or output but some, the objects of an I/O delay) | reader |
remove_propagated_clock |
1.1 † | Applied | reader |
set |
Tcl | Tcl | — |
set_case_analysis |
1.1 | Applied (sta: through its logic simulation, with the design) |
sta, sta-si |
set_clock_gating_check |
1.2 | Kept | — |
set_clock_group |
1.7 | Applied (spelling of set_clock_groups) |
sta, sta-si |
set_clock_groups |
1.7 | Applied | sta, sta-si |
set_clock_latency |
1.1 | Applied (sta-si: only as included in an I/O delay (-source_latency_included, -network_latency_included)) |
sta, sta-si |
set_clock_sense |
1.7 | Kept | — |
set_clock_transition |
1.1 | Applied | sta, sta-si |
set_clock_uncertainty |
1.1 | Applied | sta, sta-si |
set_current_design |
1.1 † | Applied (read as current_design; check warns when it names a design other than the netlist's top) |
reader |
set_data_check |
1.4 | Applied (-rise / -fall: kept, not applied) |
sta-si |
set_disable_timing |
1.1 | Applied | sta-si |
set_dont_touch |
1.1 † | Read past | — |
set_drive |
1.1 | Kept | — |
set_driving_cell |
1.1 | Applied | sta, sta-si |
set_false_path |
1.1 | Applied | check, sta, sta-si |
set_fanout_load |
1.1 | Kept | — |
set_hierarchy_separator |
1.2 | Applied (/ only: another stops the read) |
reader |
set_ideal_latency |
1.7 | Kept | — |
set_ideal_network |
1.7 | Kept (SDC's -no_propagate and the reference's -no_propagation both read) |
— |
set_ideal_transition |
1.7 | Kept | — |
set_input_delay |
1.1 | Applied | check, sta, sta-si |
set_input_transition |
1.1 | Applied | sta, sta-si |
set_level_shifter_strategy |
1.6 | Kept | — |
set_level_shifter_threshold |
1.6 | Kept | — |
set_load |
1.1 | Applied | sta, sta-si |
set_logic_dc |
1.1 | Applied (sta: through its logic simulation, with the design) |
sta, sta-si |
set_logic_one |
1.1 | Applied (sta: through its logic simulation, with the design) |
sta, sta-si |
set_logic_zero |
1.1 | Applied (sta: through its logic simulation, with the design) |
sta, sta-si |
set_max_area |
1.1 | Kept | — |
set_max_capacitance |
1.1 | Applied | rsz |
set_max_delay |
1.1 | Applied | check, sta |
set_max_dynamic_power |
1.4 | Kept | — |
set_max_fanout |
1.1 | Applied | rsz |
set_max_leakage_power |
1.4 | Kept | — |
set_max_time_borrow |
1.1 | Kept | — |
set_max_transition |
1.1 | Applied | rsz |
set_min_capacitance |
1.1 | Kept | — |
set_min_delay |
1.1 | Applied | check, sta |
set_min_porosity |
1.4 | Read past | — |
set_min_pulse_width |
2.0 | Kept | — |
set_multicycle_path |
1.1 | Applied | check, sta, sta-si |
set_operating_conditions |
1.1 | Kept | — |
set_output_delay |
1.1 | Applied | check, sta, sta-si |
set_port_fanout_number |
1.1 | Kept | — |
set_propagated_clock |
1.1 | Applied | sta, sta-si |
set_resistance |
1.1 | Kept | — |
set_sense |
2.1 ‡ | Kept (without -type: read as -type clock, the SDC 2.1 default; -type data is not modelled) |
— |
set_timing_derate |
1.5 | Applied | sta, sta-si |
set_unit |
1.7 | Applied (spelling of set_units) |
reader |
set_units |
1.7 | Applied | reader |
set_voltage |
1.8 | Kept | — |
set_wire_load_min_block_size |
1.1 | Kept | — |
set_wire_load_mode |
1.1 | Kept | — |
set_wire_load_model |
1.1 | Kept | — |
set_wire_load_selection_group |
1.1 | Kept | — |
Commands outside the SDC versions the reference parser declares, found in constraint files written for other tools (a synthesis tool's collection commands among them):
| Command | Status | Applied by |
|---|---|---|
delete_clock |
Applied | reader |
delete_generated_clock |
Applied | reader |
derive_clock_uncertainty |
Unhandled (known, not run: it stops the read) | — |
derive_pll_clocks |
Unhandled (known, not run: it stops the read) | — |
foreach_in_collection |
Applied | reader |
get_attribute |
Not applied | — |
get_object_name |
Applied | reader |
index_collection |
Not applied | — |
load_of |
Not applied (with the libraries and a netlist: the library pin's capacitance; without them, not applied) | — |
set_clock_jitter |
Unhandled | — |
set_disable_clock_gating_check |
Kept | — |
set_path_margin |
Kept | — |
set_pvt |
Kept | — |
sizeof_collection |
Not applied | — |
sort_collection |
Not applied | — |
unset_case_analysis |
Applied | reader |
unset_clock_groups |
Applied | reader |
unset_clock_latency |
Applied | reader |
unset_clock_transition |
Applied | reader |
unset_clock_uncertainty |
Applied | reader |
unset_data_check |
Applied | sta-si |
unset_disable_clock_gating_check |
Kept | — |
unset_disable_timing |
Applied | sta-si |
unset_input_delay |
Applied | reader |
unset_output_delay |
Applied | reader |
unset_path_exceptions |
Applied | reader |
unset_propagated_clock |
Applied | reader |
unset_timing_derate |
Applied | reader |
write_sdc |
Not applied (an output command, not a constraint: listed as not applied) | — |
Options SDC declares that the reference timer does not have. They are read, never refused:
| Command | Option | Status |
|---|---|---|
all_inputs |
-clock |
Kept |
all_inputs |
-level_sensitive |
Kept |
all_inputs |
-edge_triggered |
Kept |
all_outputs |
-clock |
Kept |
all_outputs |
-level_sensitive |
Kept |
all_outputs |
-edge_triggered |
Kept |
all_registers |
-no_hierarchy |
Kept |
all_registers |
-slave_clock_pins |
Kept |
all_registers |
-master_slave |
Kept |
current_instance |
-instance |
Applied |
get_ports |
-hierarchical |
Applied |
set_data_check |
-rise |
Kept |
set_data_check |
-fall |
Kept |
set_driving_cell |
-clock |
Kept |
set_driving_cell |
-clock_fall |
Kept |
set_false_path |
-rise_throough |
Applied |
set_false_path |
-fall_throough |
Applied |
set_input_delay |
-level_sensitive |
Kept |
set_load |
-substract_pin_load |
Applied |
set_max_dynamic_power |
-unit |
Kept |
set_max_leakage_power |
-unit |
Kept |
set_operating_conditions |
-object_list |
Kept |
set_output_delay |
-level_sensitive |
Kept |
set_timing_derate |
-min |
Kept |
set_timing_derate |
-max |
Kept |
set_wire_load_selection_group |
-group_name |
Kept |
Options newer releases of the vendor's SDC parser declare in SDC 1.8, and its public release in no version (†). They are read, never refused:
| Command | Option | Reference timer | Status |
|---|---|---|---|
get_cells |
-filter |
has it | Applied |
get_cells |
-quiet |
has it | Applied |
get_clocks |
-quiet |
has it | Applied |
get_lib_cells |
-filter |
has it | Applied |
get_lib_cells |
-quiet |
has it | Applied |
get_lib_pins |
-filter |
has it | Applied |
get_lib_pins |
-quiet |
has it | Applied |
get_libs |
-filter |
has it | Applied |
get_libs |
-quiet |
has it | Applied |
get_nets |
-filter |
has it | Applied |
get_nets |
-quiet |
has it | Applied |
get_pins |
-filter |
has it | Applied |
get_pins |
-of_objects |
has it | Applied |
get_pins |
-quiet |
has it | Applied |
get_ports |
-filter |
has it | Applied |
get_ports |
-quiet |
has it | Applied |
set_load |
-rise |
has it | Applied |
set_load |
-fall |
has it | Applied |
set_load |
-internal |
lacks it | Kept |
set_load |
-allow_negative_cap |
lacks it | Kept |
set_max_capacitance |
-clock_path |
lacks it | Kept |
set_max_capacitance |
-data_path |
lacks it | Kept |
set_max_capacitance |
-rise |
lacks it | Kept |
set_max_capacitance |
-fall |
lacks it | Kept |
set_max_capacitance |
-force |
lacks it | Kept |
set_max_capacitance |
-sms_scenarios |
lacks it | Kept |
set_max_transition |
-data_path |
has it | Applied |
set_max_transition |
-delay_penalty |
lacks it | Kept |
set_max_transition |
-force |
lacks it | Kept |
Reference-timer options no SDC version declares. They are read as the reference reads them, and check names each one a file uses, at info level (the file is not portable SDC):
| Command | Option |
|---|---|
all_inputs |
-no_clocks |
get_clocks |
-filter |
get_lib_cells |
-of_objects |
get_lib_pins |
-of_objects |
get_ports |
-nocase |
get_ports |
-of_objects |
group_path |
-critical_range |
set_clock_sense |
-clock |
set_driving_cell |
-cell |
set_false_path |
-reset_path |
set_max_delay |
-ignore_clock_latency |
set_max_delay |
-probe |
set_max_delay |
-reset_path |
set_min_delay |
-ignore_clock_latency |
set_min_delay |
-probe |
set_min_delay |
-reset_path |
set_multicycle_path |
-reset_path |
set_port_fanout_number |
-max |
set_port_fanout_number |
-min |
set_units |
-distance |
The Tcl, collection and take-back commands such files use are described under Constraints above.
The version dates follow the public Synopsys reference SDC parser (SDC 1.1 to 2.0, the files
above), except set_sense (‡) and the commands and options the vendor's releases declare and the
public one does not (†).
set_data_check -rise and -fall take their SDC meaning: the check applies to that edge of the
data only. Nothing here times a one-edge check, so:
checkreports it as a warning, not an error, and the file is still written back whole:`set_data_check -rise`: kept and written back; it can change timing, and no checker or timer here applies it. With--fail-on-warningthe exit code is 1.- The
sta-sitimer skips that check, rather than applying it to both edges, and warns:set_data_check: -rise (a check on one data edge) is not modelled; not applied. Its other data checks are applied. - The
statimer does not modelset_data_checkand refuses a file that has one.
set_sense is the SDC 2.1 command for a sense on a clock or data path, and the replacement for
set_clock_sense, per the reference timer's documentation. Both are read, and both are Kept:
written back, and applied by no timer here.
| Option | What it does here |
|---|---|
-type clock |
A clock sense: read and written back. |
no -type |
The same as -type clock, the SDC 2.1 default, and written back as -type clock. |
-type data |
Not modelled: the command is not applied and not written back (a warning). |
-positive, -negative, -stop_propagation |
Read and written back; no timer here applies them. |
-clocks |
The clocks the sense is for, written back as [get_clocks …]. With -clock, -clocks wins. |
-pulse |
Read and written back; not modelled. |
check reports each kind with its count. For set_sense -stop_propagation [get_pins u1/Q]:
`set_sense`: kept and written back; it can change timing, and no checker or timer here applies it(a warning);`set_sense` no -type: applied as clock (SDC 2.1 default); the reference timer ignores it without -type(information): aset_sensewithout-typeis read as clock, as SDC 2.1 says, where the reference timer applies nothing for it.
For set_sense -type data, a warning: `set_sense -type data`: not modelled, not applied; the reference timer does not apply it either.
set_clock_sense is still accepted, is not reported as deprecated, and is written back as given
(set_clock_sense, its options spelled out in full). Its -clock option is a reference-timer
extension, reported at information level like the others.
Neither timer applies a clock sense. The sta timer refuses a file that has one:
set_sense / set_clock_sense: not modelled by this timer. The sta-si timer reads the file, does
not apply the sense, and warns once per kind with its count: sdc: `set_sense` (2 commands): kept and written back; not applied by this timer (STA-SDC-NOT-APPLIED).
Only / is supported. Any other separator stops the read (exit 2), since names written with it
would not resolve. For set_hierarchy_separator . the message is:
`set_hierarchy_separator .`: only `/` is supported, and names using another separator would not resolve; the file is not read
| Exit | Meaning |
|---|---|
| 0 | check: no errors (with --fail-on-warning, no warnings either); write: written; diff: equivalent; map: every object carried |
| 1 | a finding: check found an error (or a warning with --fail-on-warning), write refused, diff found a change or a command it could not compare, map found an object it could not resolve or carry, or waivers export left a waiver out |
| 2 | usage, or an input that could not be read |
Every run leaves a vyges-events trail on stderr: JSONL when piped, text on a terminal
(VYGES_LOG_FORMAT=json|text forces either). Each event names the SDC file as its object:
- one
SDC-<finding>per check finding, such asSDC-UNCONSTRAINED-OUTPUT; - one
SDC-NOT-WRITTENper dropped command (fordiff, per command not compared; formap, per command whose objects were not read); - one
SDC-DIFF-CHANGEper changedifffound, at warn when it loosens or is unknown, else info; - with
--rtl, oneSDC-RTL-VIEWsummary (registers named by their RTL name, and kept) and one per register that kept its instance name, with why; - one
SDC-MAP-BITper RTL bit indexmap(or--rtl) translated by its declared range, oneSDC-MAP-ALIASper RTL name it matched by an alias, with the rules used, oneSDC-MAP-UNKNOWNper pattern it found naming nothing (or indexing outside its declared range, or an ambiguous alias, with its candidates), and oneSDC-MAP-UNCARRIEDper object it could not carry, with why; - a summary,
SDC-CHECK,SDC-WRITE,SDC-DIFForSDC-MAP; - at debug,
SDC-READ, which says what the file held.
-q/-v follow the suite's verbosity contract. They repeat: -q warnings, -qq errors,
-qqq silent, -v debug, -vv trace. A flag overrides VYGES_LOG, which, inherited from
vyges, sets the whole command tree.
vyges-sdc --help lists every option. --version reports the build's commit.
vyges-sdc --describe publishes a structured contract. vyges mcp then advertises three typed
tools an agent can drive:
sdc, the check, which assertsclean;sdc.write, which assertscompleteand has the written SDC as its artifact;sdc.diff, which assertsequivalent.
Once installed (vyges install physical), they are discoverable.
The checker, the writer and the comparison are the vyges-loom implementations (sdc_lint,
Sdc::write and sdc_diff).
The timing engine runs the same code, so vyges-sdc check and the timer always agree about what
a file says.
Apache-2.0 (LICENSE).
{ "command": "check", "clean": true, "errors": 0, "warnings": 2, "endpoint_coverage_pct": 50.0, "findings": [ { "severity": "warning", "code": "endpoint-coverage", "message": "endpoint coverage 50.0% — 1 of 2 constrained (1/1 registers clocked, 0/1 outputs with a delay)" }, { "severity": "warning", "code": "unconstrained-output", "message": "output `dout` has no set_output_delay" } ] }