使用 SDC 动态约束简化设计复用

author-image

作者

创建可在许多设计中重用的设计区块或 HDL 组件时,可能需要创建与其匹配的 SDC 约束。创建重用组件的设计者无需进行编辑的约束是很有用的。约束应该是通用的,因此无论在设计层次中的哪个位置对区块进行实例化,约束都可以运行。约束也应该是动态的,因此无论如何连接设计区块,约束都可以运行。如果必须通过手动编辑约束来反映设计更改,如果设计者在进行设计更改后未更新约束,那么约束将无法同步。

此设计示例介绍了创建 SDC 动态约束的技术,这些技术解决了以下两个问题:

  • 确定直接连接到底层模块的顶层 I/O 的名称
  • 在底层模块中的逻辑上创建生成时钟

图 1 中的图表显示了此示例中一个非常简单的设计。它包含一个名为 reusable_block 的可重用设计区块的两个实例,如黄色部分所示。图 2 显示了 reusable_block 设计的内容。reusable_block 用作源同步输出总线的双倍数据速率时钟。其输出必须连接到顶层输出。由于将输出用作源同步时钟,因此 reusable_block 的约束必须包含生成时钟。

图 1.设计示例的示例电路。

图 2.reusable_block 的内容。

确定顶层 I/O 名称

reusable_block 约束必须适应对顶层 I/O 名称所做的更改。因此,必须在编译或时序分析期间确定顶层 I/O 名称。get_fanouts Tcl 命令返回端口或寄存器 ID 的集合,这些端口或寄存器是指定名称的扇出。get_fanouts Tcl 命令使用编译或时序分析期间已有的时序网表,因此无论扇出节点的名称如何,它都会以动态方式确定连接情况。以下 Tcl 代码显示了如何使用 get_fanouts 获取顶层输出,即底层寄存器的直接扇出。

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

您无需知道底层寄存器的完整层次名称,因为可以使用通配符和可重用设计区块中已有的已知层次部分对其进行匹配。此页面的最后一个代码示例显示了如何匹配底层寄存器名称的示例。

在图 1 的设计中,底层模块输出引脚直接连接到一个顶层输出。以下 Tcl 代码添加了错误检查,以确保底层寄存器仅扇出到一个位置,并且扇出位置是输出端口。此 Tcl 代码应为约束 reusable_block 的 SDC 文件的一部分。

# 获取底层寄存器的扇出
set fanout_collection [get_fanouts $low_level_register_name]

# 确保仅有一个扇出
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."
}

# 获取扇出节点的名称
foreach_in_collection fanout_id $fanout_collection { break }
set fanout_name [get_node_info -name $fanout_id]

# 确保扇出节点是输出端口
if { [catch { get_port_info -is_output_port $fanout_id } is_output] } {
    # 出错 - 未扇出到端口
    return -code error "$low_level_register_name fans out to $fanout_name \
        which is not a port"
} elseif { ! $is_output } {
    # 未出错,但端口不是输出端口
    return -code error "$fanout_name is not an output port"
} else {
    set top_level_io_name $fanout_name
}

# top_level_io_name 是 low_level_register_name 的唯一扇出,并且是
# 输出端口

创建生成时钟

必须基于馈入双倍数据速率输出寄存器的时钟将源同步输出时钟定义为生成时钟。由于可以在采用任何时钟方案的设计中对设计区块进行实例化,因此必须在未在设计中手动输入任何时钟相关信息的情况下创建生成时钟。

以下 SDC 命令显示了一个简单的方法,此方法在层次中的位置未知的情况下,为图 1 中的设计的源同步输出时钟创建生成时钟。

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

这是一种简单、直接的方法,适用于为处于设计层次中任何位置的 reusable_block 进行单次实例化,但无法处理多次实例化或多时钟情况。当时钟方案未知时,生成时钟约束应能处理在馈入设计区块的单个时钟信号上定义了多个时钟的情况。在支持不同 I/O 协议速度的设计或支持冗余时钟切换的设计中,单个时钟信号上通常存在多个时钟。以上简单生成时钟示例在多时钟情况下无法运行,因为它不包含用于区分多个源时钟的 -master_clock 选项。

要处理多次实例化,请使用循环为每次实例化创建唯一生成时钟。要处理多时钟情况,请使用名为 get_clocks_driving_pin 的自定义程序,如“馈入引脚的时钟”设计示例所述。要使用此自定义程序,必须从“馈入引脚的时钟”设计示例页面对其进行复制。可以将其另存为添加到项目中的单独 SDC 文件,或者将其复制并粘贴到包含约束可重用区块的所有其它约束的一个 SDC 文件中。如果将其另存为添加到项目中的 SDC 文件,请确保它列于使用 get_clocks_driving_pin 自定义程序的任何 SDC 文件的前面。

以下 Tcl 代码显示了如何在图 1 所示设计中的底层寄存器驱动的顶层输出上创建生成时钟约束。生成时钟将顶层输出用作目标,将 altddio_output 寄存器的 muxsel 引脚用作源。此代码使用循环对设计中的 reusable_block 的所有实例化进行迭代,并使用嵌套循环和 get_clocks_driving_pin 自定义程序来处理多时钟情况。此代码假设已定义了 get_clocks_driving_pin 程序。

# 对于 reusable_block 的每次实例化,get_pins 返回一个 muxsel 引脚
# foreach_in_collection 对每个 muxsel 引脚进行迭代
foreach_in_collection pin_id [get_pins -compatibility_mode \
    *|reusable_block_clock_out|altddio_out_component|auto_generated|ddio_outa[0]|muxsel] {

    # 对于 reusable_block 的每次实例化,pin_name 包含 muxsel 引脚的
    # 完整设计层次
    set pin_name [get_node_info -name $pin_id]
    
    # 在不进行错误检查的情况下,使用以上代码获取
    # 顶层输出的名称
    foreach_in_collection port_id [get_fanouts $pin_name] { break }
    set port_name [get_node_info -name $port_id]
    
    # 馈入 altddio_output 寄存器的时钟可以有多个
    # 馈入 muxsel 引脚的每个时钟需要
    # 一个生成时钟。馈入 muxsel 引脚的每个时钟都是主时钟。
    foreach master_clock [get_clocks_feeding_pin $pin_name] {

        post_message "Creating generated clock on $port_name fed by $pin_name"
        # 使用适当的主时钟创建生成时钟。
        # 在 reusable_block 的当前实例化中,源是 altddio_output 单元的
        # muxsel 引脚。
        # 名称是主时钟与 muxsel 引脚
        # 完整层次名称的组合。
        # 目标是顶层端口,即 muxsel 引脚的扇出。
        create_generated_clock -add -master_clock $master_clock \
            -source [get_pins $pin_name] -name ${master_clock}-${pin_name} \
            [get_ports $port_name]
    }
}

在项目中包含的 SDC 文件中使用此代码,reusable_block 的所有实例化都会自动受到生成时钟的约束。生成时钟始终是正确的且处于最新状态,即使在以下情况下也是如此:

  • 在设计层次中对 reusable_block 进行了实例化或将其移动到设计层次中的其它点
  • 重命名了顶层 I/O
  • 设计者在设计中使用了多个时钟定义