Simplify Design Reuse with Dynamic SDC Constraints

author-image

By

When you create a design block or HDL component that can be reused in many designs, it may be necessary to create SDC constraints to go with it. It is useful to create constraints that don't require editing by the designer reusing the component. Constraints should be generic, so they work regardless of where the block is instantiated in the design hierarchy, and dynamic so they work regardless of how the design block is connected. If constraints must be manually edited to reflect design changes, they will become out of sync if the designer makes design changes without also updating the constraints.

This design example covers techniques for creating dynamic SDC constraints that address the following two issues:

  • Determining the name of a top-level I/O connected directly to a low-level module
  • Creating generated clocks on logic in low-level modules

The diagram in Figure 1 shows a very simple design for this example. It includes two instances of a reusable design block named reusable_block, shown in yellow. Figure 2 shows the contents of the reusable_block design. reusable_block functions as a double data rate clock for a source-synchronous output bus. Its output must be connected to a top-level output. Constraints for reusable_block must include generated clocks, because the output functions as a source-synchronous clock.

Figure 1. Sample circuit for design example.

Figure 2. Contents of reusable_block.

Determining Top-level I/O Names

Constraints for reusable_block must accommodate changes to top-level I/O names. Therefore, the top-level I/O name must be determined during compilation or timing analysis. The get_fanouts Tcl command returns a collection of IDs representing ports or registers that are fanouts of a specified name. The get_fanouts Tcl command uses a timing netlist that exists during compilation or timing analysis, so it dynamically determines the connectivity regardless of the names of the fanout nodes. The following Tcl code shows how to use get_fanouts to get the top-level output that is a direct fanout of a low-level register.

foreach_in_collection fanout_id [get_fanouts $low_level_register_name] { break }
set top_level_io_name [get_node_info -name $fanout_id]

The full hierarchy name of the low-level register does not have to be known, because you can use a wildcard and a known portion of the hierarchy that exists in the reusable design block to match it. The last code example on this page shows an example of how to match the low-level register name.

In the design in Figure 1, the low-level module output pin is connected directly to one top-level output. The following Tcl code adds error checking to ensure that the low-level register fans out to only one location and that the fanout location is an output port. This Tcl code should be part of the SDC file that constrains reusable_block.

# Get the fanouts of the low-level register
set fanout_collection [get_fanouts $low_level_register_name]

# Ensure there is only one fanout
set num_fanouts [get_collection_size $fanout_collection]
if { 1 != $num_fanouts } {
    return -code error "$low_level_register_name fans out to $num_fanouts \
        nodes but must fan out to one."
}

# Get the name of the fanout node
foreach_in_collection fanout_id $fanout_collection { break }
set fanout_name [get_node_info -name $fanout_id]

# Ensure the fanout node is an output port
if { [catch { get_port_info -is_output_port $fanout_id } is_output] } {
    # There was an error - it does not fan out to a port
    return -code error "$low_level_register_name fans out to $fanout_name \
        which is not a port"
} elseif { ! $is_output } {
    # There is no error, but the port is not an output port
    return -code error "$fanout_name is not an output port"
} else {
    set top_level_io_name $fanout_name
}

# top_level_io_name is the only fanout of low_level_register_name and it is
# an output port

Creating Generated Clocks

A source-synchronous output clock must be defined as a generated clock, based on the clock that feeds the double data rate output registers. The generated clock must be created without any manually entered information about clocks in the design, because the design block could be instantiated in any design with any clocking scheme.

The following SDC command shows a simple way to create a generated clock for the source-synchronous output clock for the design in Figure 1, when the location in the hierarchy is not known.

create_generated_clock -name reusable_generated -source [get_pins \
    *|reusable_block_clock_out|altddio_out_component|auto_generated|ddio_outa[0]|muxsel] \
    $top_level_io_name

It is a straightforward approach that works for a single instantiation of reusable_block anywhere in the design hierarchy, but it does not handle multiple instantiations or multiclock situations. When the clocking scheme is unknown, the generated clock constraint should be able to handle situations where multiple clocks have been defined on a single clock signal that feeds the design block. Multiple clocks on a single clock signal often exist in designs that support different I/O protocol speeds, or designs that support clock switchover for redundancy. The simple generated clock example above fails in multiclock situations because it does not include the -master_clock option to distinguish between multiple source clocks.

To handle multiple instantiations, use a loop to create unique generated clocks for each instantiation. To handle multiclock situations, use a custom procedure called get_clocks_driving_pin, described in the Clocks Feeding a Pin design example. To use the custom procedure, you must copy it from the Clocks Feeding a Pin design example page. You can save it as a separate SDC file that is added to the project, or copy and paste it into one SDC file with all other constraints that constrain a reusable block. If you save it as an SDC file that is added to the project, ensure it is listed before any SDC file that uses the get_clocks_driving_pin custom procedure.

The following Tcl code shows how to create generated clock constraints on top-level outputs driven by low-level registers in the design shown in Figure 1. The generated clocks use the top-level outputs as their targets, and the muxsel pins of altddio_output registers as their sources. The code uses a loop to iterate through all the instantiations of reusable_block in the design, and a nested loop to handle multiclock situations with the get_clocks_driving_pin custom procedure. It assumes the get_clocks_driving_pin procedure has been defined already.

# get_pins returns one muxsel pin for each instantiation of reusable_block
# foreach_in_collection iterates over each muxsel pin
foreach_in_collection pin_id [get_pins -compatibility_mode \
    *|reusable_block_clock_out|altddio_out_component|auto_generated|ddio_outa[0]|muxsel] {

    # pin_name has the full design hierarchy of the muxsel pin for one
    # instantiation of reusable_block
    set pin_name [get_node_info -name $pin_id]
    
    # Use the code shown above, without error checking, to get
    # the name of the top level output
    foreach_in_collection port_id [get_fanouts $pin_name] { break }
    set port_name [get_node_info -name $port_id]
    
    # There can be multiple clocks feeding the altddio_output register
    # One generated clock is required for each clock that feeds
    # a muxsel pin. Each clock feeding the muxsel pin is a master clock.
    foreach master_clock [get_clocks_feeding_pin $pin_name] {

        post_message "Creating generated clock on $port_name fed by $pin_name"
        # Create the generated clock with the appropriate master clock.
        # The source is the muxsel pin of the altddio_output cell in
        # the current instantiation of reusable_block.
        # The name is a combination of the master clock and the
        # full hierarchy name of the muxsel pin.
        # The target is the top-level port that is the fanout of the muxsel pin.
        create_generated_clock -add -master_clock $master_clock \
            -source [get_pins $pin_name] -name ${master_clock}-${pin_name} \
            [get_ports $port_name]
    }
}

With this code in an SDC file included in the project, all instantiations of reusable_block are automatically constrained with generated clocks. The generated clocks are always correct and up to date, even in the following situations:

  • reusable_block is instantiated at or moved to other points in the design hierarchy
  • Top-level I/Os are renamed
  • The designer uses multiple clock definitions in the design