synopsys/standard design constarints (sdc)

Synopsys/Standard design constraints (SDC)

SDC is a subset of the design constraint commands already supported by many CAD tools. SDC was agreed on as a standard, since diff tool vendors had their own synthesis/timing constraint cmds, which made it difficult to port these constraints. Since most of the constraints for synthesis, timing, etc are standard (i.e define clock, port delays, false paths, etc), it just made sense to have standard constraints that would be supported by all vendors. Synopsys and Cadence Synthesis, timing, PnR, etc tools support these SDC cmds.
SDC versions are 1.2, 1.3 .. 2.0. In write_sdc (in both synopsys and cadence tools), we can specify version of sdc file to write (default is to use latest version). SDC cmds started with having design constraint cmds only, but over time expnaded to include cmds pertaining to reporting, collection, get objects, etc.

It's been 2022, and still no one including me, knows the full form of SDC !! Synopsys, which had these cmds initially in their synthesis/timing tools, allowed them to become a standard. Cadence and other companies grudgingly accepted it, and called it Standard design constraints (but don't mention the full form anywhere). But Synopsys website still refer to these as "Synopsys Design constraints".

Before we learn these cmds, let's go over few basics of design that these cmds work on.

Objects:

Objects are anything in design like ports, cells, nets, pins, etc. Valid objects are design, port , cell, pin, net , lib, lib_cell, lib_pin, and clock. Each of these objects may have multiple attribute. As an ex, each gate may have 100's of attributes as gate_name, size, pins, delay_arcs, lib_name, etc. These objects are further put into several class as ports, cells, nets, pins, clocks, etc. Most commands operate on these objects.

Collections:

Synopsys applications build an internal database of objects and attributes applied to them. Cadence applications also build a similar internal database. but the internal representation may be different, and process to access them may be different. So arguments of some of the sdc commands may be different across different vendors, even though they may support the basic sdc cmd. Same sdc file in Synopsys may not be used directly in cadence tools, as many of these collection cmds (cmds that work on collection of objects) may have some part of code (cmds that were used to make a collection) that may not be recognized by Cadence tools as valid. The situation is improving, but this is something that needs to be kept in mind. Always read the SDC manual of a vendor to find out what sdc cmds and syntax it supports.

Definition: A collection is a group of objects exported to the Tcl user interface. Collections are tcl extension provided by EDA vendors (Synopsys/Cadence) to support list of objects in their Tcl API. Most of the design cmds work on these collection of objects. In Tcl language, we have 2 composite data types, "list" and "array", that allows us to put multiple elements into a single group. Collections can be thought of as similar to Tcl list, though they are not interchangeable, as internals of the 2 are different. However, many cmds take both list and collection as i/p, not differentiating bwteen the 2. This is done for user convenience. However, internally the list is converted to a collection, and o/p of the cmd is always given out as collection. Visually collections look like list (when displayed on the screen), but they are NOT list.

A set of commands to create and manipulate collections is provided as an integral part of the user interface. The collection commands encompass two categories: those that create collections of objects for use by another command, and other that queries objects for viewing. These two types of collection cmds are:

1. create/manipulate objects: add_to_collections, append_to_collection, remove_from_collection, sizeof_collection, foreach_in_collection, sort_collection, compare_collection, copy_collection, filter_collection, etc are few common collection cmds. These cmds work on collection of objects to manipulate that object list and create new collection of objects. As such, these cmds may be used as i/p to other cmds that expect collection of objects as it's i/p. Collections return a pointer to that collection (i.e 0x78 is what you see on the screen when creating a collection), which is used by other cmds. In all these collection cmds below, the collections provided to the cmd themselves remain unchanged: A new collection is created which can either be passed to other cmds, or may be assigned to a var, which becomes the new collection.

  1. create/remove collection: Collections can be created by starting with an empty collection, and using "add_to_collection" or "append_to_collection" (both being similar, although append is more efficient in some situations as per PT manual). Adding an implicit list of only strings or heterogeneous collections to the empty collection generates an error message, because no homogeneous collections (collection of same class, i.e either all ports, or all cells, etc) are present in the object_spec list. Finally, as long as one homogeneous collection is present in the object_spec list, the command succeeds, even though a warning message is generated. If the base collection to which we are adding the new collection is not empty, then heterogenous objects (i.e ports, cells) from second collection are added to the base collection only if base collection also has heterogenous objects. So, rules get complicated, and to see what got added/deleted, we should always  instead of relying on add or append cmd, it's easier to just make a collection using get_ports, get_cells, etc which implicitly make a collection of items. 
    1. add_to_collection: Creates a new collection by concatenating objects from the base collection and the second collection. base_collection needs to be a collection, while second collection may be collection or object list. The base collection and second collection remain unmodified, i.e these collections are appended and new collection is made which can either be passed to other cmds, or may be assigned to a var, which becomes the new collection. This cmd returns AUB (i.e union of A and B)
      • Syntax: add_to_collection <base_coll> <second_coll> => -unique option removes the duplicate objects from the new collection. 
        • ex in PT: pt_shell> add_to_collection "" [list port1 port2] => This adds design objects "port1" and "port2" to the empty collection and creates a new collection which has no name, and if not passed to a cmd, does nothing. We can create a new coll, by giving it a name as => set var1 [add_to_collection "" [list port1 port2]]
        • pt_shell> set port_coll [list port1 port2] => Here new collection port_coll  is formed which has the 2 ports in that coll. These ports may or may not be valid. Here we didn't have to explicitly create a collection as the o/p of [...] is converted to a  collection, the pointer to which is set to port_coll. echo $port_coll returns a pointer as 0x87 (in Cadence tools) or _sel3555 (in Synopsys tools). However on screen it shows {"port1", "port2"}. We could also use get_ports cmd whose o/p is a port list collection. That's a preferred way as that guarantees correct collection being provided as i/p to collection list. i.e
        • pt_shell> set port_coll [get_ports [list port_a port_b[*]]  => Here get_ports o/p is a coll of => {"port_a", "port_b[1]", "port_b[1]"}. We set this coll to port_coll
        • pt_shell> set port_coll [list $port1 $cell2] => here port1, cell2 need to be valid object names or collection.  may give an error as variables $port1 $cell2 might be pointers to collections and list , etc .One way to guarantee that is to have these var from the o/p of get_port, get_cell or similar cmds which return collection as o/p. If not, we get an Error in PT => At least one collection required for argument 'object_spec' to add_to_collection when the 'collection' argument is empty (SEL-014)
    2. append_to_collection: Since add_to_collection doesn't modify the original collection, this cmd allows you to append the original coll, which is useful in many cases. This command can be much more efficient than the add_to_collection command if you are building up a collection in a loop.
      • Syntax: append_to_collection var_name <obj_or_coll> => -unique option removes the duplicate objects from the new collection. NOTE: var may or may not be defined, may be a existing coll, or an empty coll. If the var does exist and it does not contain a collection, it is an error. We do NOT have $ in front of var name, as we are modifying or defining this var (we are not accessing the value of the var). This var becomes a coll once a coll is appended to it.
        • ex: append_to_collection my_ports [get_ports in*] => Here my_ports is a var, to which [get_ports in*] coll get added.
    3. remove_from_collection: To remove elements from collection.  Any element in 2nd coll is removed from base_coll. Similar to "add_to_collection", base collection and second collection remain unmodified. A new collection is created, which can be assigned to a var. Here, collections can't contain any list of objects, which is how add_to_collection behaved (it allowed lists as option)
      • Syntax: remove_from_collection <base_coll> <obj_coll> => o/p is A - (A∩B) since objects in <obj_coll> are removed from <base_coll>. -intersect option does the opposite => it removes obj in base coll that are not found in <obj_coll>. So, with this option, instead of providing o/p as A - (A∩B), it's A∩B (that's why it's called intersect). So, with remove and add cmd, we can find out all combo => AUB, A∩B, A - (A∩B) and B - (A∩B)
        • ex: remove_from_collection [concat [get_cells *] [get_ports *in*]] [get_cells I_*] => removes specified cells from coll of cells and ports
        • ex: set dports  [remove_from_collection [all_inputs] CLK] => all_inputs creates a collection of all i/p ports from which CLK is removed. T/he remaining collection pointer is assigned to var dports.
  2.  compare_collections: To compare 2 coll if they have same elements or not, we use this cmd. 0 indicates success (i.e same elements), while any other number indicates failure (diff elements). This behaviour is consistent with "string compare" in tcl, which returns 0 on match.
    • Syntax: compare_collection <coll1> <col2l> => With -order_dependent option, the collections are considered to be different if the objects are ordered differently.
      • compare_collections $c1 [get_ports out*] => Here c1 coll and ports coll are compared, and if they have same contents (order doesn't matter), then 0 is returned.
  3. foreach_in_collection: To iterate over the objects in a collection, use the foreach_in_collection command. You cannot use the Tcl- supplied foreach iterator to iterate over the objects in a collection, because the foreach command requires a list, and a collection is not a list. The o/p of any cmd that returns a collection is actually a pointer to that collection. The arguments of the foreach_in_collection command are similar to those of foreach: an iterator variable, the collection over which to iterate, and the script to apply at each iteration.
    • Ex: set A [get_ports] => returns these {"port[0]", "IN2", "SPI_CLK0"} => Object names are displayed just for readability. A still holds the pointer value. echo $A => returns a pointer _sel3555
    • foreach_in_collection tmp $A {echo [get_object_name $tmp]} => returns these  3 ports = port[0] IN2 SPI_CLK0. echo $tmp will return pointer value _sel3555 3 times. This is because tmp is just assigned the pointer value
    • foreach_in_collection tmp _sel3555 {echo [get_object_name $tmp]} => we get same result as above, since A is just storing the pointer to collection
    • create_fillers -lib_cells $A => These collections ($A) work just fine in cmds which expect to get a collection pointer as argument. No need to iterate thru each element of collection. Many cmds accept both collection as well as list for argument. So, no issues with either one.
    • set_false_path -to [get_pins mod1/*] => Here get_pins returns a pointer which points to a collection with all pins of mod1. Since set_false_path takes objects in it's args, it takes this collection of pins as an argument, and sets false path to all the pins, i.e set_false_path -to {mod1/pinA, "mod1/pinB[0]", ...}. However, if we want, we can iterate thru each pin of "get_pins" cmd using foreach_in_collection loop.
      • ex: foreach_in_collection tmp [get_pins mod1/*] {set_false_path -to [get_object_name $tmp]} => Here each object in collection is assigned to var "tmp" one by one, and then false path set to each of them separately. i.e set_false_path -to mod1/pinA, set_false_path -to "mod1/pinB[0]", ... => see how set_false_path is done separately for each object of collection. So, collections can be used as one, or can be divided into individual elements.
    • get_ports _sel3555 => since _sel3555 is a pointer to collection, cmd get_ports gets collection of ports from the collection pointed by _sel3555. That returns collection of ports similar to what "get_ports" returns by itself. This is just a convoluted way to show that it works.
  4. sizeof_collection: Very useful cmd to figure out the size of collection w/o iterating thru the whole list. To find the size of collection, one way is to use foreach_in_collection cmd above. We increment a counter inside the loop, and when that loop exits, the counter shows the number of elements in the collection. sizeof_collection provides an easier way to do that by using this single cmd instead of a loop. Sometimes collections will be empty, and in such cases, we want to know beforehand. Otherwise our collection cmds will error out since all these cmds expect valid collection pointer. In such cases, sizeof_collection is useful too.
    • ex: if { [sizeof_collection [get_ports -quiet SPI*]] != 0} { foreach_in_collection tmp [get_ports -quiet SPI*] { do operation } } else { echo "collection empty" } => If we directly used foreach_in_collection on an empty list, then the tool would report an error saying the collection is empty. We avoid that by using if else.
  5. filter_collection: Most used cmd to filter objects from any collection based on specified filtering criteria. It allows objects to be filtered out before they are ever included in the collection. An alternative -filter option for many cmds is also provided for many apps, see details in later section. 
    • ex: filter_collection  [get_cells *] "is_hierarchical == true && ref_name=~*AN2*" => get_cells creates a collection of all cells in design, and then filters out those whose hierarchy is set to "true", and the reference cells have pattern AN2 int hem. So, it finally gets all AND2 leaf cells. NOTE: double quotes are only at start and end of filtering (not with each filter expr)
    • ex: filter_collection $coll -regexp "full_name =~ ^${softip}/eco_\[a-z\]+_icd/.* or full_name =~ ^${myip}/.*" => here regex used, so we use .* instead of *. Also, keywords "and", "or" may be used isntead of &&, ||, etc. $coll is a collection generated from o/p of some prior cmd.

2. query objects:

We can do "echo" of a list, and it will print the list, but with collection, a simple "echo" won't return the list. When we do "echo" on collection, we only get a pointer to collection. However, on screen, we do see o/p similar to echo of list (where it's listing names of objects in collection), whenever we run any cmd that outputs a collection. This is done by vendor tools just for convenience purpose. CAD tools by default call an implicit "query_objects" cmd, whenever any cmd that outputs a collection is run. A default limit of 100 is set as the max number of objects that can be displayed (controlled by a variable "collection_result_display_limit" whose default value of 100 can be changed to anything we want). Though this works for most viewing purpose, we can't use this printed o/p within a script as the cmd itself just returns a pointer to collection and not a list of objects in the collection. In order to see what's stored in collection, we can also use a built in collection proc "query_objects". "query_objects" takes as i/p either a collection or a pattern. By default, query_object displays names of objects (-verbose option displays class of each object as cell, net, etc too). Again, this is also for display purpose only, and it's o/p can't be used in scripts, as it always returns an empty string. For getting names of objects to be used in a script, we have to use function like "get_object_name" on the collection. There are many other cmds for getting other attributes of each object in the collection.

  • ex: query_objects [get_cells o*] => ["or1", "or2"]. get_cells returns a collection which is then passed to query_objects which gets the names and outputs it as a list. Here query_objects is passed a collection as it's i/p. Here o/p is in default legacy format
  • ex: query_objects -class cell U* => [U1 U2]. When i/p to query_objects is a pattern, we have to specify a class as cell, net, etc (classes are application specific). Here o/p is in tcl format (i.e tcl list with no commas etc), which is possible by setting thisapp var => set_app_var query_objects_format Tcl

All below cmds expect collection, and so can only be passed a pointer to collection. Names or patterns as options will give an error. This is diff than query_objects cmd above which can take patterns as an i/p too.

  1. get_object_name => convenient way to get the full_name attribute of objects in a collection. When multiple objects passed as i/p, o/p is returned as a list. Cadence supports this as this cmd was added in SDC for compatibility while reading in SDC files with non-SDC constraints. However, it just returns specified i/p args provided.
    1. ex: pt_shell> get_object_name [get_cells i*] => ["inx1", "ind2"]. get_cells returns a collection which is then passed to get_object_name.
    2. ex: get_object_name "in1/cell2" => errors out since "in1/cell2" is not a collection pointer. To make it work, do: get_object_name [get_cells in1/cell2]

-------------