Using Tcl commands to select logic elements

This page belongs to a series of pages about timing. The previous pages explained the theory behind timing calculations, discussed the clock period constraint, showed the principles of timing closure, and began looking into the Tcl environment. This page explains the commands that allow timing constraints to be specific.


Recall that the purpose of timing constraints is to ensure that the timing requirements of all paths in the design are fulfilled. Different paths may have different requirements, so each timing constraint must be directed to the relevant group of paths, and not include anything else.

The only way to define paths is by referring to logic elements that are related to these paths. It's therefore important to have the ability to write accurate expressions that select the correct groups of logic elements. In other words, it's necessary to correctly use commands like get_clocks, get_ports and get_cells, so that their results are exactly what is needed.

This page walks through the fundamental techniques for describing groups of logic elements. Almost everything here is specific to the SDC syntax. I shall also assume that a Tcl command-line interface is available. This is true for Vivado and Quartus, as well as most FPGA tools of recent time.

Some FPGA vendors openly declare that Synopsys' software is integrated into their tools, so naturally these tools use the same Tcl commands. Vivado, on the other hand, is not considered to be derived from Synopsys. And yet, there are a few striking similarities between the two, in particular regarding the Tcl interface. Quartus seems to have been developed independently, and its Tcl interface are therefore a bit different.

At first glance, it may appear as if this page gets too much into the details of Tcl scripting. The truth is the opposite: Accuracy is extremely important, and this can only be achieved by understanding exactly how the commands are interpreted. In fact, this page only explains the basic principles. There is no substitute to reading the documentation.

Getting guidance from the FPGA tools

It's often difficult to get started with writing timing constraints, because there are so many details to take care of. The FPGA tools can help with this.

The "help" command can be used in the Tcl console for viewing the documentation on a Tcl command. This is often exactly the same information as in the official documents (e.g. in pdf format).

Most FPGA tools have a GUI interface for creating timing constraints automatically. The working method is commonly used to select the type of timing constraints that is required. The next step is selecting the relevant logic elements from a list. The result is one or several timing constraints that are added to the SDC file.

Using a wizard of this sort is helpful for getting hints about the Tcl syntax. Occasionally, the constraints that are created automatically can also be used as is. However, in most cases it's worth to resist the temptation to use the wizard's output, and instead carefully think through how the purpose of the timing constraint is best achieved. It's also important to consider how the project is expected to evolve, and make sure that the timing constraints remain correct over time. A quick session with a GUI wizard is unlikely to achieve this.

Some FPGA tools also have a GUI interface for finding logic elements in the design. When this interface is used, the Tcl command that corresponds to the requested search is often shown. This is a convenient method for obtaining Tcl expressions for finding specific logic elements. Once again, these expressions should be treated as a starting point for further work.

A third feature of most FPGA tools is a Tcl command-line console which allows typing commands (or use Copy-Paste) and see the results on the screen. This allows testing the commands and their search patterns, and see which objects are found. This can help in verifying that the search pattern is correct.

In conclusion, the FPGA tools can assist with creating the Tcl command. But as already said, the Tcl commands that are created by the tools should be no more than a basis for writing accurate search patterns that are guaranteed to work correctly over time.

Now, when we know the lazy way to create timing constraints, it's time to learn how to do this properly.

The netlist: A short reminder

Before getting more specific about the Tcl environment, I'd like to make a brief reminder about netlists.

The most common file format for netlists is EDIF, but many FPGA tools have their own specific format too. Usually, the synthesizer creates the netlist, even though the tools may alter it during later stages as well. This file describes the logic design in terms of its basic components and the connections between these components. It's like a wiring diagram, but with a textual representation rather than a graphic image.

The components in a netlist are called "cells". The vast majority of cells are something like a LUT, another small element of combinatorial logic or a flip-flop. Also, instantiations of black boxes in the Verilog code (e.g. IP cores) are represented as a cell in the netlist. Other cells are PLLs, block RAMs, and large logic elements ("hard IPs"): PCIe blocks, MGT transceivers, processors etc.

Each cell has a number of pins: These pins are just like the external connection points of a physical electronic component. But don't confuse this with the FPGA's external I/O: Both the cells and the pins exist inside the FPGA.

The interconnect in the netlist consists of nets. These are like physical wires. For example, when a signal is defined with "wire" in Verilog, this results in a net. A net connects between two or more pins, and by doing so, it is ensured that these pins have the same logic level all the time.

The representation of logic elements as objects

When the FPGA tools run through an implementation of a project, what actually happens is that Tcl scripts are executed. This is true for Vivado and Quartus and several other FPGA tools. Even with software that works differently, it's still correct to make this assumption: The illusion that everything is one big Tcl script is created by the API for constraints and for other script files.

In the environment for this Tcl script (whether imaginary or not), all logic elements are represented as objects that are created from different classes. These objects can be accessed by the timing constraints in an SDC file (or Xilinx' XDC file). Likewise, Tcl commands in the Tcl command-line console and Tcl scripts have access to these objects.

These are five Tcl commands that are supported by all FPGA tools that work with the SDC syntax. These commands are used to find objects of different types (i.e. different classes). I've already used them in the previous examples of timing constraints. In fact, it's quite impossible to write meaningful timing constraints without these commands.

Without any arguments, these commands find all objects of the relevant type. Later on, we shall see how to refine the search.

Except for these five commands, each FPGA tools has its own additional commands and additional objects. For example, Vivado has additional commands such as all_ffs, all_registers, all_inputs, all_outputs, all_rams and several others of this sort. Quartus supports some of these, and also has get_registers, get_keepers, get_nodes, get_fanins and get_fanouts and so on.

The importance of these objects is that they are used in timing constraints to refer to logic elements in the FPGA design. They can also be used in Tcl scripts to obtain information, for example a clock's frequency. Each FPGA tool has its own API for accessing these objects in Tcl scripts. For example, this Tcl command can be used in Vivado to obtain a clock period:

> get_property PERIOD [get_clocks clk]

And the same in Quartus:

> get_clock_info -period [get_clocks clk]

Despite these differences, most FPGA tools agree on the syntax and meaning of timing constraints in SDC format. The information about the API that each tool supports is usually found in user guides that are have titles that relate to Tcl scripting or Timing Closure.

The examples on this page are based upon Vivado, unless otherwise noted.

A few notes on Tcl

Tcl is an archaic language, however because it's well-established in the field of logic design, this language is not expected to disappear anywhere soon. Luckily, it's possible to do useful things with Tcl even without knowing it too well.

First thing, which I've already mentioned briefly, is square brackets ("[" and "]"). In Tcl, this means to execute the command in the square brackets, and put the result in their position. For those who are familiar with shell scripting or Perl, this is the same as backticks. For example, in this command, get_port is substituted by the port object that has the name "clk":

create_clock -period 4.000 -name clk [get_ports clk]

Likewise, this could have been written like this:

set the_clk_port [get_ports clk]
create_clock -period 4.000 -name clk $the_clk_port

As shown in this second example, variables are defined and assigned a value with the "set" command. To access the variable's value, the dollar sign ($) is used. Once again, similar to shell script and Perl.

Regarding curly braces ("{" and "}"), it's a different story: Like several other languages, their meaning depends a lot on their context. In Tcl, one of the less expected meanings of curly braces is that the enclosed string should remain untouched. In other words, there should be no substitutions, and whitespaces should be considered like any character. For example, the same timing constraint could have been written like this:

create_clock -period {4.000} -name {clk} [get_ports {clk}]

In this example, the curly braces are completely unnecessary, and this command means exactly the same as before. Unnecessary curly braces are unfortunately common, and often they mean nothing, exactly as in this example.

A tip for using the Tcl console

It often happens that the number of objects that are found is large, which makes it difficult to read the output of the search command. This can be solved with simple Tcl commands. How to do this exactly depends on which tool is used. With Vivado, this command prints out all cells in the design. Each cell is printed in a separate line, so even if there are many cells, the output is still readable:

> join [get_cells -hierarchical] \n
[ ... ]

The "join" command puts a newline between each element in the array that get_cell produces.

With Quartus, the same result is achieved with:

join [query_collection -all [get_cells -hierarchical] ] \n

Or by using query_collection more wisely:

query_collection -all -report_format [get_cells -hierarchical]

Finding specific elements

After a long introduction, it's finally time to talk about what is really interesting. For the sake of the examples below, refer the following Verilog code, which is also used later:

module top(
    input clk,
    input foo,
    output reg bar_reg,
    output reg baz	   
    reg foo_reg;
    reg bar;
    reg baz_metaguard;
    wire pll_clk_8, pll_clk_6;
   clk_wiz_1 pll_i

always @(posedge pll_clk_8)
  foo_reg <= foo;
always @(posedge pll_clk_6)
    bar <= !foo_reg;
    bar_reg <= bar;

always @(posedge clk)
    baz_metaguard <= bar;
    baz <= baz_metaguard;

As mentioned at the top of this page, the accuracy of timing constraints depends on selecting the correct groups of logic elements. It's therefore possible and necessary to narrow down the search results from the five get_* commands that were mentioned above. There are several ways to do that, however the most common way is based upon the object's name. The simplest pattern is to find a single object which has the exact name that we're looking for. For example, in Vivado's Tcl console:

> get_ports clk

Likewise, it's possible to find all objects that have names that match a specific pattern:

> get_pins pll_i/*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2

Note that the output in both of these examples are objects. In Vivado's Tcl console, the names of these objects are printed out as a matter of convenience.

Even more important, note that each FPGA tool behaves slightly different with regards to these search patterns. Vivado is used in the examples here. The same principles apply for other tools as well.

The asterisk ("*") is a wildcard character, and substitutes any number of characters. A question mark ("?") substitutes one character. This works just like wildcards with file names.

Note that just like file names, the wildcards don't match with the hierarchy separator (e.g. "/" with Vivado and "|" with Quartus).

There is also a similarity between the hierarchical path and directories of files: The search is made in relation to the top-level hierarchy, which is similar to the root directory of a file system. So, for example:

> get_pins */clk_*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
> get_pins pll_i/clk_out?
pll_i/clk_out1 pll_i/clk_out2

The need to specificity the exact position of each logic element in the hierarchy is often a significant drawback: The logic elements that we want to find are often in different positions. This is solved with "-hierarchical": When this flag is present, the search for the pattern is made in all positions in the hierarchy.

In general, using wildcards to find logic elements is not recommended. The only exceptions are when the search pattern is very simple or when there is no other choice. There is a separate page that explains how to use wildcards and "-hierarchical", and also shows this method's limitations.

Using -filter

There are several disadvantages to using search patterns that are based upon wildcards. The most significant disadvantage is that the hierarchy must be defined precisely or not defined at all. One problem is that it's often desired to limit the search to objects that belong to a sub-hierarchy. However, this is not possible with wildcards, and the "-hierarchical" option doesn't solve this problem.

For this reason and others, the preferred way to define search patterns is with the -filter option. This option is accompanied with a boolean expression. When this option is used, only the search results for which this expression is true remain.

For example,

> get_pins */clk_*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
> get_pins */clk_* -filter {name =~ *2*}

In this example, the property called "name" was examined on each of the objects that were found by virtue of the wildcard. The object remained in the search result only if this property matched with "*2*". In other words, only if the name of the object had "2" in it.

This example isn't interesting from a practical point of view. It's much more interesting when "-hierarchical" is used: Without any search pattern, all objects are found. In other words, this command finds all pins in all hierarchies:

get_pins -hierarchical

From this point, it's possible to narrow down the search results with -filter:

> get_pins -hierarchical -filter {name =~ pll_i/*/*out1 }
> get_pins -hierarchical -filter {name =~ pll_i/*out1 }
pll_i/clk_out1 pll_i/inst/clk_out1
> get_pins -hierarchical -filter {name =~ *out1 }
pll_i/clk_out1 pll_i/inst/clk_out1

Note that the meaning of the curly braces ("{" and "}") is just that the part that is inside them should not be modified by the Tcl interpreter.

Also note that the search pattern belongs to the -filter option, and it behaves differently: It treats the "name" as a property of an object. Therefore, all characters are treated equal: "/" has no special significance. It doesn't matter that "/" is the hierarchy separator. All characters, including "/", can match with the wildcard ("*"). Likewise, all characters, including "/", can be used in the search pattern. This is of course not true without -filter.

The fact that any character can match with "*" makes -filter a stronger tool, but this advantage is also a possibility for mistakes: It's easy to forget that an innocent "*" can accidentally match with both "pll_i/clk_" and "pll_i/inst/clk_", as shown in the example above.

A correct use of this feature is for finding a logic element with a known name somewhere in the hierarchy:

> get_pins -hierarchical -filter {name =~ */clkout2_buf/O }

This is correct if we are sure that there is only one cell in the design that has the name "clkout2_buf". By using this command, the output pin of this cell is always found, even if the module that contains this logic element is moved around in the project's hierarchy. In a real-life scenario, it's better to pick a more unique name than "clkout2_buf".

The same method works with get_cells and get_nets, for example:

> get_cells -hierarchical -filter {name =~ */clk*_buf}
pll_i/inst/clkf_buf pll_i/inst/clkout1_buf pll_i/inst/clkout2_buf

But -filter works on all properties, not just the name. So this command finds all registers that have "bar" inside their name:

get_cells -hier -filter {primitive_type =~ register.*.* && name =~ *bar*}

Note the "&&" operator, which means logical AND (just like in Verilog and C).

In this command, "primitive_type" and "name" are just names of properties. The "=~" operator makes a comparison, and allows wildcards.

Recall that the properties of an object can be listed with the "report_property" command (in Vivado).

So -filter is flexible to work with. Unfortunately, it's not available with all FPGA tools.

Note that Vivado has a command named filter, which does the same operation as -filter. So the following two commands are equivalent:

set result [get_cells -hierarchical -filter {name =~ *_reg}]
set result [filter [get_cells -hierarchical] {name =~ *_reg}]

The second format is useful when a list of objects is stored in a variable. So the two commands above are also equivalent to this:

set all_cells [get_cells -hierarchical]
set result [filter $all_cells {name =~ *_reg}]

Using -regex

Those who are familiar with regular expressions may want to use this option. It is usually not a good idea to do this, mainly because it makes the timing constraints more difficult for other people to understand. FPGA tools that support regular expressions usually support the -filter option as well, so it's almost always better to use -filter.

Recall that the main problem with the usual search pattern is that the hierarchy separator doesn't match with the wildcard.

So -regexp can solve this, as shown in this couple of examples:

> get_pins -hierarchical -regexp {.+/clk_out[123]}
pll_i/clk_out1 pll_i/clk_out2 pll_i/inst/clk_out1 pll_i/inst/clk_out2
> get_pins {pll_i/[^/]+/clk[^/]+} -hierarchical -regexp
pll_i/inst/clk_in1 pll_i/inst/clk_out1 pll_i/inst/clk_out2

The first command demonstrates that "." matches with any character. This includes "/" (the hierarchy separator).

The second command demonstrates how "[^/]+" is used to match with anything except for a hierarchy separator. So this allow controlling the exact depth into the hierarchy of the search results. This command also demonstrates that the search pattern is not an argument of -regexp, but that -regexp changes the syntax of the search pattern.

Note that the regular expression must match with the entire name of the object. In other words, the tools implicitly add a "^" at the beginning of the regular expression, and add a "$" at the end.

But don't use -regex if there is another way to achieve the same result. Most other FPGA designers will not be able to understand the search pattern.

Using -of_object

Instead of finding objects according to their names, -of_object allows finding objects according to their relation with other objects. In most cases "-of_objects" roughly means "is connected to".

For example, in order to find all pins that are connected to a net:

> get_pins -of_objects [get_nets bar]
bar_reg_reg/D baz_metaguard_reg/D bar_reg__0/Q

Or all pins of a cell:

> get_pins -of_objects [get_cells bar_reg_reg]
bar_reg_reg/Q bar_reg_reg/C bar_reg_reg/CE bar_reg_reg/D bar_reg_reg/R

Or the pin that is the origin of a clock:

> get_pins -of_objects [get_clocks clk_out1_clk_wiz_1]

The same method works for finding nets. For example, which nets are connected to a specific pin?

> get_nets -of_objects [get_pins bar_reg_reg/C]

Or, which nets are connected to the related cell?

> get_nets -of_objects [get_cells bar_reg_reg]
bar_reg_OBUF pll_clk_6 <const1> bar <const0>

Likewise, it's possible to search for cells this way. For example, which cells are connected to @pll_clk_6?

> get_cells -of_objects [get_nets pll_clk_6]
bar_reg__0 bar_reg_reg pll_i

Note that "pll_i" is the instantiation name of the Clock Wizard's IP. It's probably not a wanted result of the search. So maybe narrow down the search result to just flip-flops?

> get_cells -of_objects [get_nets pll_clk_6] -filter {primitive_type =~ register.*.*}
bar_reg__0 bar_reg_reg

So far, the examples above with -of_objects demonstrated how pins, nets and cells can refer to each other. But it's also possible to find clock objects with this option:

> get_clocks -of_objects [get_nets pll_clk_6]
> get_clocks -of_objects [get_cells bar_reg_reg]
> get_clocks -of_objects [get_pins bar_reg_reg/C]

These three commands show how the clock is found according to the logic element that is connected to it. Note that when this logic element is a cell, there can be more than one result. For example:

> get_clocks -of_objects [get_cells pll_i]
clk clk_out1_clk_wiz_1 clk_out2_clk_wiz_1

Recall that "pll_i" is an IP, so its pins are the ports of this module:

> get_pins -of_objects [get_cells pll_i]
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2

So in order to find a specific clock, get_pins is safer:

> get_clocks -of_objects [get_pins pll_i/clk_out2]

But of course, the clock object on an external I/O port is best found with:

> get_clocks -of_objects [get_ports clk]

Or, with the shorter command, which is equivalent:

> get_clocks [get_ports clk]

Speaking of get_ports, it also works with -of_objects. Refer to the documentation regarding the possibilities, which are specific to get_ports. The possibilities that are similar to the other commands are quite pointless, for example:

> get_ports -of_objects [get_nets clk]

In summary, -of_objects is an excellent method for selecting specific logic elements. This is true in particular because of the difficulties with searching according to the object's name, which is the next topic below.

Unfortunately, many FPGA tools don't support -of_objects, which leaves no choice but to rely on less reliable methods.

The problem with searching by name

As already discussed above, the accuracy of timing constraints depends on the accuracy of finding logic elements. At least some of these search results depend on an object's name. Let's see how this can cause trouble.

For example, "get_ports clk" is used to make the connection between a clock object and the signal that is present at a specific I/O pin. This I/O pin is represented by a port object that has the name "clk":

create_clock -period 4.000 -name clk [get_ports clk]

But why did this port object get the name "clk"? The answer is related to how the synthesizer created the netlist: The name of the top-level port in the Verilog code was chosen as the name of the netlist's top-level port as well. This is a obvious choice, so any proper synthesizer will do the same.

But what about the names of cells? Let's take the register with the name "foo_reg" in the Verilog code above. What is the name of the cell object that represents this register's flip-flop? Vivado's synthesizer chose the name "foo_reg_reg" for this object. So it's clear that this synthesizer tends to add a "_reg" suffix to the name in the Verilog code. That sounds like a rule that can be relied upon. But another synthesizer would probably do something different.

But what about the register with the name "bar"? The name of the relevant cell object should have been "bar_reg", but the synthesizer made a different choice: "bar_reg__0". This is because there's a register called "bar_reg" in the Verilog code. So to avoid a name collision, the synthesizer picked a slightly different name: It added "_reg__0" instead of just "_reg". This simple example demonstrates the problem with relying on the names of objects.

To make things even worse, suppose that the timing constraint was written before the register with the name "bar_reg" was added to the Verilog code. In this case, the cell object that relates to @bar gets the name "bar_reg", as usual. So the timing constraint would use this name for the object. At a later stage, the register with the name "bar_reg" is added to the design. As a result, the name of the requested cell object changes from "bar_reg" to "bar_reg__0". The timing constraint, which relies on the name "bar_reg", is suddenly incorrect. With some luck, the tools will issue a warning about this.

There are other possible reasons why using names of objects can go wrong. For example, the tools may duplicate a register automatically if its fan-out exceeds a limit. When this happens, the additional register may not be included in the timing constraint, because the new register's name doesn't match with the search pattern.

A more serious problem is when logic elements are included in timing constraints by accident. For example, this can happen with logic elements that belong to IP blocks. Because we don't have any control on the names of these logic elements, it's possible that these names accidentally match with the pattern in a timing constraint.

Accidental inclusion of logic elements in timing constraints can also be the result of laziness: The timing constraints are usually written along with adding new features to the logic design. If the search pattern is written by trial and error, the names of future logic elements may not be taken into consideration. Hence when new logic is added, the names of some of its logic elements can unintentionally match with the existing timing constraints.

How to avoid mistakes with timing constraints

The first and most important rule is that it's not enough to test which logic elements match with the search pattern of a timing constraint. Even if you make a complete list of these logic elements and review this list carefully, this doesn't guarantee anything regarding logic that will be added later. Neither does such a review guarantee that the timing constraints will continue to work as expected if the names of the objects are changed by the synthesizer or during another stage of the implementation.

It's therefore important to treat the search patterns like mathematical expressions: It's not enough that these search patterns work as expected right now. And if they don't work as expected, it's not enough to make a small fix to make them work. Rather, there must be a logic explanation to why the search pattern is correct, and why it is very likely to remain correct over time.

It's also important that the search patterns rely on things that are unlikely to change. For example, the tools don't change the names of instantiations in the Verilog code. This holds true for instantiations of modules, IPs and primitives. It's therefore safe to rely on the hierarchical path when it consists only of names of instantiations that are written in the Verilog code. It is not as safe to use names that were created inside an IP block. To explain this, let's look at this command:

get_clocks -of_object [get_pins pll_i/inst/clkout1_buf/O]

This is a method to obtain a clock object by referring to the output pin of the global clock buffer that distributes the clock. The question is whether we can be sure that this will work in the long run.

The problem with this method is that "inst" and "clkout1_buf" are names of instantiations that are made inside the Clocking Wizard IP. Even though these names are unlikely to change, there is no guarantee that they won't.

One possible solution is to find the Verilog code that implements the IP, and include this Verilog in the project directly. This ensures that nothing will change in the future.

An alternative is to look at the instantiation of the IP in Verilog. Recall that it was:

 clk_wiz_1 pll_i

Because this is an instantiation of a black box, each of the ports has a pin in the netlist. The names can't change, because this is how the ports of the IP are identified. It's therefore guaranteed that the pin with the name "pll_i/clk_out1" makes the connection with @pll_clk_8. So this is a safe way to obtain the clock object:

get_clocks -of_object [get_pins pll_i/clk_out1]

Note that this will probably not work if clk_wiz_1 is just another Verilog module in the project: In this case, no pins are created on behalf of this module's instantiation (because the synthesizer usually implements connections of ports by merging nets). A possible solution could be using the names that appear in lower levels of the hierarchy.

So there are a few kinds of names that can be relied upon:

It's better to fail big

The worst situation is when a timing constraint is almost correct: When there are only a few paths that are not included. Or when there are only a few paths that are included in the timing constraint by mistake. Errors of this sort are the most difficult to find.

This is the main reason why timing constraints that are short, concise and having a mathematical style are better. If there is a timing constraint for each small group of logic elements, it's easy for a mistake to sneak into this long list of rules.

To demonstrate this idea, let's go back to the example that was shown above for finding a clock object:

get_clocks -of_object [get_pins pll_i/inst/clkout1_buf/O]

As mentioned above, the problem with this expression is that if the instantiation of pll_i moves to another position in the hierarchy, no clock object will be found. How bad will that be?

Suppose that this clock object is stored in a Tcl variable like this:

set my_clock [get_clocks -of_object [get_pins pll_i/inst/clkout1_buf/O]]

And then $my_clock is used in many timing constraints, so many paths are involved. In this case, it's actually not a big problem if $my_clock suddenly doesn't contain any clock object because of a bug: There's a good chance that this mistake will be noticed soon, because a lot of things will go wrong.

But if $my_clock is used in only one timing constraint, and this constraint is intended to solve a small problem that rarely has any effect, this is a bad situation. Odds are that this mistake will go unnoticed.

In conclusion: A well-written timing constraint should either work perfectly or not work at all.

This page showed how to accurately select logic elements. In the next page, this knowledge is used to define selective timing constraints.

Copyright © 2021-2024. All rights reserved. (6f913017)