Vivado: Random notes about the XDC constraints file

This post was written by eli on October 13, 2014
Posted Under: FPGA,Vivado

These are a few jots about constraining in Vivado. With no particular common topic, and in no particular order. Note that I have another post on similar topics.

Setting the default IOSTANDARD for all ports

In a design where all ports have the same IOSTANDARD, it’s daunting to set them for all. So if there’s just one exception, one can go

set_property IOSTANDARD LVCMOS33 [get_ports -filter { LOC =~ IOB_* } ]
set_property IOSTANDARD LVDS_25 [get_ports clk_100_p]

It’s important to do this after placement constraints of the ports, because the LOC property is set only in conjunction with setting package_pin. Filtering based upon LOC is required to avoid inclusion of MGT ports in the get_ports command, and in turn fail the set_property command altogether (yielding not only a critical warning, but none of the IOSTANDARDs will be set).

Tell the truth about your heat sink

Vivado makes an estimation of the junction temperature, based upon its power estimation. That’s the figure that you want to keep below 85°C (if you’re using a commercial temperature version of the FPGA).

With all my reservations on the accuracy of the power estimation, and hence the temperature it calculates based upon in, it makes sense to tell Vivado about the chosen cooling solution. Otherwise, it assumes a heatsink with a healthy fan above it. So if you like to live on the edge, like me, and work without a noisy fan, these two lines in the XDC file tell Vivado to adjust the effective Junction-to-Air thermal resistance (Θ_JA).

set_operating_conditions -airflow 0
set_operating_conditions -heatsink low

It’s also possible to set Θ_JA explicitly with -thetaja. Try “help set_operating_conditions” at the Tcl prompt for a list of options.

Frankly speaking, the predicted junction temperature stated on the power report is probably rubbish anyhow, even if the power estimation is accurate. The reason is that there’s a low thermal resistance towards the board: If the board remains on 25°C, the junction temperature will be lower than predicted. On the other hand, if the board heats up from adjacent components, a higher temperature will be measured. In a way, the FPGA will serve as a cooling path from the board to air. With extra power flowing through this path, the temperature rises all along it.

For example, the temperature I got with the setting above on a KC705, with the fan taken off, was significantly higher (~60°C) than Vivado’s prediction (44°C) on a design that had little uncertainty (90% of the estimated power was covered by static power and GTXs with a fixed rate — there was almost no logic in the design). The junction temperature was measured through JTAG from the Hardware Manager.

So the only thing that really counts is a junction temperature after 15 minutes or so.

Search patterns for finding elements

Pattern-matching in Vivado is slash-sensitive. e.g.

foreach x  [get_pins "*/*/a*"] { puts $x }

prints elements pins three hierarchies down beginning with “a”, but “a*” matches only pins on the top level.

The “foreach” is given here to demonstrate loops. It’s actually easier to go

join [get_pins "*/*/a*"] "\n"

To make “*” match any character “/” included, it’s possible, yet not such a good idea, to use UCF-style, e.g.

foreach x [get_pins -match_style ucf "*/fifo*"] { puts $x }

or a more relevant example

get_pins -match_style ucf */PS7_i/FCLKCLK[1]

The better way is to forget about the old UCF file format. The Vivado way to allow “*” to match any character, including a slash, is filters:

set_property LOC GTXE2_CHANNEL_X0Y8 [get_cells -hier -filter {name=~*/gt0_mygtx_i/gtxe2_i}]

Another important concept in Vivado is the “-of” flag which allows to find all nets connected to a cell, all cells connected to a net etc.

For example,

get_nets -of [get_cells -hier -filter {name=~*/gt_top_i/phy_rdy_n_int_reg}]

Group clocks instead of a lot of false paths

Unlike ISE, Vivado assumes that all clocks are “related” — if two clocks come from sources, which the tools have no reason to assume a common source for, ISE will consider all paths between the clock domains as false paths. Vivado, on the other hand, will assume that these paths are real, and probably end up with an extreme constraint, take ages in the attempt to meet timing, and then fail the timing, of course.

Even in reference designs, this is handled by issuing false paths between each pair of unrelated clocks (two false path statements for each pair, usually). This is messy, often with complicated expressions appearing twice. And a lot of issues every time a new clock is introduced.

The clean way is to group the clocks. Each group contains all clocks that are considered related. Paths inside a group are constrained. Paths between groups are false. Simple and intuitive.

set_clock_groups -asynchronous \
  -group [list \
     [get_clocks -include_generated_clocks -of_objects [get_pins -hier -filter {name=~*gt0_mygtx_i*gtxe2_i*TXOUTCLK}]] \
     [get_clocks -include_generated_clocks "gt0_txusrclk_i"]] \
  -group [get_clocks -include_generated_clocks "drpclk_in_i"] \
  -group [list \
     [get_clocks -include_generated_clocks "sys_clk"] \
     [get_clocks -include_generated_clocks -of_objects [get_pins -hier -filter {name=~*/pipe_clock/pipe_clock/mmcm_i/*}]]]

In the example above, three clock groups are declared.

As a group often consists of several clocks, each requiring a tangled expression to pin down, it may come handy to define a list of clocks, with the “list” TCL statement, as shown above.

Another thing to note is that clocks can be obtained as all clocks connected to a certain MMCM or PLL, as shown above, with -of_objects. To keep the definitions short, it’s possible to use create_generated_clock to name clocks that can be found in certain parts of the design (create_clock is applied to external pins only).

If a clock is accidentally not included in this statement, don’t worry: Vivado will assume valid paths for all clock domain crossings involving it, and it will probably take a place of honor in the timing report.

Telling the tools what the BUFGCE/BUFGMUX is set to

Suppose a segment like this:

BUFGCE clkout1_buf
 (.O   (slow_clk),
 .CE  (seq_reg1[7]),
 .I   (clkout1));

To tell the tools that the timing analysis should be made with the assumption that BUFGCE is enabled,

set_case_analysis 1 [get_pins -hier -filter {name=~*/clkout1_buf/CE0}]
set_case_analysis 1 [get_pins -hier -filter {name=~*/clkout1_buf/S0}]

The truth is that it’s redundant in this case, as the tools assume that CE=1. But this is the syntax anyhow.

Constant clock? Who? Where? Why?

One of the things to verify before being happy with a design’s timing (a.k.a. “signing off timing”), according to UG906 (Design Analysis and Closure Techniques) is that there are no constant clocks nor unconstrained internal endpoints. But hey, what if there are? Like, when running “Report Timing Summary”, under “Check Timing” the number for “constant clock” is far from zero. And the timing summary says this:

2. checking constant clock
 There are 2574 register/latch pins with constant_clock. (MEDIUM)

3. checking pulse_width_clock
 There are 0 register/latch pins which need pulse_width check

4. checking unconstrained_internal_endpoints
 There are 0 pins that are not constrained for maximum delay.

 There are 5824 pins that are not constrained for maximum delay due to constant clock. (MEDIUM)

Ehm. So which clock caused this, and what are the endpoints involved? It’s actually simple to get that information. Just go

check_timing -verbose -file my_timing_report.txt

on the Tcl prompt, and read the file. The registers and endpoints are listed in the output file.

Floorplanning (placement constraints for logic)

The name of the game is Pblocks. Didn’t dive much into the semantics, but used the GUI’s Tools > Floorplanning menus to create a Pblock and auto-place it. Then saved the constraints, and manipulated the Tcl commands manually (i.e. the get_cells command and the choice of slices).

create_pblock pblock_registers_ins
add_cells_to_pblock [get_pblocks pblock_registers_ins] [get_cells -quiet -hierarchical -filter { NAME =~  "registers_ins/*" && PRIMITIVE_TYPE =~ FLOP_LATCH.*.* && NAME !~  "registers_ins/fifo_*" }]
resize_pblock [get_pblocks pblock_registers_ins] -add {SLICE_X0Y0:SLICE_X151Y99}

The snippet above places all flip-flops (that is, registers) of a certain module, except for those belonging to a couple of submodules (excluded by the second NAME filter) to the bottom area of a 7V330T. The constraint is non-exclusive (other logic is allowed in the region as well).

The desired slice region was found by hovering with the mouse over a zoomed in chip view of an implemented design.

The tools obeyed this constraint strictly, even with post-route optimization, so it’s important not to shoot yourself in the foot when using this for timing improvement (in my case it worked).

To see how the logic is spread out, use the “highlight leaf cells” option when right-clicking a hierarchy in the netlist view to the left of a chip view. Or even better, use Tcl commands on the console:

highlight_objects -color red [get_cells -hierarchical -filter { NAME =~  "registers_ins/*" && PRIMITIVE_TYPE =~ FLOP_LATCH.*.* && NAME !~  "registers_ins/fifo_*" }]

The first command removes existing highlight. There’s an -rgb flag too for selecting the exact color.

There’s also show_objects -name mysearch [ get_cells ... ] which is how the GUI’s “find” operation creates those lists in GUI to inspect elements.

Add a Comment

required, use real name
required, will not be published
optional, your blog address