获取馈入引脚的时钟

author-image

作者

此设计示例展示了可以在 SDC 文件中使用的自定义程序,此程序返回馈入引脚的所有时钟的列表。如果需要在不知道设计中其它时钟名称的情况下创建生成时钟,此程序将非常有用。“使用 SDC 动态约束简化设计重用”设计示例提供了如何使用此页面中所述的自定义程序的更多详细信息和示例。

下面是如何运行此程序的完整说明,程序的完整代码位于页面底部。要在 SDC 文件中使用 get_clocks_driving_pins 自定义程序,确保已定义此程序,然后像调用任何其它 SDC 命令一样调用此程序。要在使用此程序之前确保已对其进行定义,有两种简单的方法:

  • 将程序代码保存到单独的 SDC 文件中,并将 SDC 文件包含到项目中。
  • 在使用 get_clocks_driving_pins 自定义程序之前,将程序代码复制并粘贴到任何 SDC 文件的开头。

单独的 SDC 文件

将程序代码保存在单独的 SDC 文件中可以将代码与其它约束分开,并且更容易在其它项目中重用代码。如果使用单独的 SDC 文件,必须将包含程序的 SDC 文件添加到项目的文件列表中,并将其列在使用此程序的任何 SDC 文件前面。将其列在其它 SDC 文件前面可确保无论何时读取 SDC 文件,Quartus® II 软件都会在使用此程序之前对其进行定义。

复制和粘贴

将程序代码复制并粘贴到使用它的同一个 SDC 文件中,可减少项目中的 SDC 文件数量。如果为供其他设计者重用的模块创建 SDC 文件,最简单的方法是将所有约束和支持代码包含在一个 SDC 文件中。在第一次使用程序之前,必须将程序代码包含在 SDC 文件中,以便在使用之前对其进行定义。为了满足此要求,通常应将其放在文件的开头。

脚本运行

要获取设计中馈入引脚的所有时钟的列表,需要进行三个主要步骤:

  1. 获取所有时钟,并创建从其目标节点到目标节点上时钟的映射。
  2. 获取时钟位于指定引脚扇入路径上的节点的列表。
  3. 在此节点列表中找到距离指定引脚最近的节点,并返回此节点上的时钟。

第 1 步:获取所有时钟并创建映射

下面的 Tcl 代码获取设计中的所有时钟,并创建从节点到节点上时钟的映射(使用 Tcl 阵列)。

catch { array unset nodes_with_clocks }
array set nodes_with_clocks [list]

# 对设计中的每个时钟进行迭代
foreach_in_collection clock_id [all_clocks] {

    set clock_name [get_clock_info -name $clock_id]

    # 将每个时钟应用于节点。获取目标节点集合
    foreach_in_collection target_id [get_clock_info -targets $clock_id] {

        # 将时钟名称与其目标节点相关联
        set target_name [get_node_info -name $target_id]
        lappend nodes_with_clocks($target_name) $clock_name
    }
}

虚拟时钟没有目标节点,因此不对虚拟时钟进行映射。请保存下面列出的完整程序代码中有关目标节点类型(寄存器、引脚、单元或端口)的信息,以供日后使用。

第 2 步:获取时钟位于扇入路径上的节点

第二步是查找时钟位于指定引脚扇入路径上的节点的子集。对于每个具有时钟的节点(已在步骤 1 中找到),获取指定引脚经过此节点的扇入路径。如果有扇入路径,表明此节点位于引脚的扇入路径上。如果没有扇入路径,表明此节点不在引脚的扇入路径上。

以下 Tcl 代码对第 1 步中所有具有时钟的节点进行迭代,然后使用 get_fanins 命令确定每个节点是否位于指定引脚的扇入路径上。如果节点位于指定引脚的扇入路径上,将此节点保存在 pin_drivers 列表中。

set pin_drivers [list]

# 对在第 1 步创建的映射中的所有节点进行迭代
foreach node_with_clocks [array names nodes_with_clocks] {

    # 获取指定引脚经过当前节点的任何扇入路径
    set fanin_col [get_fanins -clock -through $node_with_clock $pin_name]

    # 如果至少有一个扇入节点,表明当前节点位于
    # 指定引脚的扇入路径上,请将其保存。
    if { 0 < [get_collection_size $fanin_col] } {
        lappend pin_drivers $node_with_clocks
    }
}

下面列出的完整程序代码使用有关节点类型的附加信息为 get_fanins 命令中的 -through 值指定特定类型的集合。

第 3 步:查找距离指定引脚最近的节点

pin_drivers 变量现在包含时钟位于指定引脚扇入路径上的所有节点的列表。在此步骤中查找距离指定引脚最近的节点。当 pin_drivers 列表中有多个节点时,代码获取列表中的前两个节点,并检查一个节点是否位于另一个节点的扇入路径上。如果位于扇入路径上,第一个节点与引脚的距离必须比第二个节点更远,以便将其从列表中移除。

while { 1 < [llength $pin_drivers] } {

    # 获取 pin_drivers 列表中的前两个节点
    set node_a [lindex $pin_drivers 0]
    set node_b [lindex $pin_drivers 1]

    # 检查 node_b 是否位于 node_a 的扇入路径上
    set fanin_col [get_fanins -clock -through $node_b $node_a]

    # 如果至少有一个扇入节点,node_b 与指定引脚的距离必须
    # 比 node_a 更远。
    # 如果没有扇入节点,node_b 与指定引脚的距离必须比
    # node_a 更近。
    if { 0 < [get_collection_size] } {

        # node_a 与引脚的距离更近。
        # 从 pin_drivers 列表中移除 node_b
        set pin_drivers [lreplace $pin_drivers 1 1]

    } else {

        # node_b 与引脚的距离更近。
        # 从 pin_drivers 列表中移除 node_a
        set pin_drivers [lreplace $pin_drivers 0 0]
    }
}

# pin_drivers 中保留的那个节点就是驱动指定引脚的节点
set node_driving_pin [lindex $pin_drivers 0]

# 查找第 1 步映射中的节点上的时钟,然后返回这些时钟
return $nodes_with_clocks($node_driving_pin

下面列出的完整程序代码使用有关节点类型的附加信息为 get_fanins 命令中的 -through 值指定特定类型的集合。

完整程序代码

get_clocks_driving_pin 自定义程序的完整代码如下所示。它包含上文并未详细说明的其它错误检查和支持功能。

proc get_clocks_feeding_pin { pin_name } {

    # 在第 1 步之前执行错误检查,确保传入程序的 pin_name
    # 仅与一个引脚相匹配。
    # 如果并非仅与一个引脚相匹配,将返回错误。
    set pin_col [get_pins -compatibility_mode $pin_name]
    if { 0 == [get_collection_size $pin_col] } {
        return -code error "No pins match $pin_name"
    } elseif { 1 < [get_collection_size $pin_col] } {
        return -code error "$pin_name matches [get_collection_size $pin_col]\
            pins but must match only one"
    }
    
    # 对程序中使用的变量进行初始化
    catch { array unset nodes_with_clocks }
    catch { array unset node_types }
    array set nodes_with_clocks [list]
    array set node_types [list]
    set pin_drivers [list]
    
    # 第 1 步:获取设计中的所有时钟,并创建从目标节点
    # 到目标节点上时钟的映射

    # 对设计中的每个时钟进行迭代
    foreach_in_collection clock_id [all_clocks] {

        set clock_name [get_clock_info -name $clock_id]
        set clock_target_col [get_clock_info -targets $clock_id]
        
        # 将每个时钟应用于节点。获取目标节点集合
        foreach_in_collection target_id [get_clock_info -targets $clock_id] {

            # 将时钟名称与其目标节点相关联
            set target_name [get_node_info -name $target_id]
            lappend nodes_with_clocks($target_name) $clock_name

            # 保存目标节点的类型,供日后使用
            set target_type [get_node_info -type $target_id]
            set node_types($target_name) $target_type
        }
    }

    # 第 2 步:获取时钟位于指定引脚扇入路径上的
    # 节点的列表

    # 对在第 1 步创建的映射中的所有节点进行迭代
    foreach node_with_clocks [array names nodes_with_clocks] {

        # 使用目标节点的类型为 get_fanins 命令中的 -through 值
        # 创建特定类型的集合。
        switch -exact -- $node_types($node_with_clocks) {
            "pin" {  set through_col [get_pins $node_with_clocks] }
            "port" { set through_col [get_ports $node_with_clocks] }
            "cell" { set through_col [get_cells $node_with_clocks] }
            "reg" {  set through_col [get_registers $node_with_clocks] }
            default { return -code error "$node_types($node_with_clocks) is not handled\
                as a fanin type by the script" }
        }

        # 获取指定引脚经过当前节点的任何扇入路径
        set fanin_col [get_fanins -clock -through $through_col $pin_name]

        # 如果至少有一个扇入节点,表明当前节点位于
        # 指定引脚的扇入路径上,请将其保存。
        if { 0 < [get_collection_size $fanin_col] } {
            lappend pin_drivers $node_with_clocks
        }
    }

    # 在第 3 步之前执行错误检查,确保设计中至少一个
    # 具有时钟的节点位于指定引脚的
    # 扇入路径上。
    if { 0 == [llength $pin_drivers] } {
        return -code error "Can not find any node with clocks that drives $pin_name"
    }
    
    # 第 3 步:从第 2 步创建的节点列表中,找到
    # 距离指定引脚最近的节点,并返回此节点上的时钟。

    while { 1 < [llength $pin_drivers] } {

        # 获取 pin_drivers 列表中的前两个节点
        set node_a [lindex $pin_drivers 0]
        set node_b [lindex $pin_drivers 1]
        
        # 使用目标节点的类型为 get_fanins 命令中的 -through 值
        # 创建特定类型的集合。
        switch -exact -- $node_types($node_b) {
            "pin" {  set through_col [get_pins $node_b] }
            "port" { set through_col [get_ports $node_b] }
            "cell" { set through_col [get_cells $node_b] }
            "reg" {  set through_col [get_registers $node_b] }
            default { return -code error "$node_types($node_b) is not handled\
                as a fanin type by the script" }
        }

        # 检查 node_b 是否位于 node_a 的扇入路径上        
        set fanin_col [get_fanins -clock -through $through_col $node_a]

        # 如果至少有一个扇入节点,node_b 与指定引脚的距离
        # 必须比 node_a 更远。
        # 如果没有扇入节点,node_b 与指定引脚的距离
        # 必须比 node_a 更近。
        if { 0 < [get_collection_size $fanin_col] } {

            # node_a 与引脚的距离更近。
            # 从 pin_drivers 列表中移除 node_b
            set pin_drivers [lreplace $pin_drivers 1 1]

        } else {

            # node_b 与引脚的距离更近
            # 从 pin_drivers 列表中移除 node_a
            set pin_drivers [lrange $pin_drivers 1 end]
        }
    }
    
    # pin_drivers 中保留的那个节点就是驱动指定引脚的节点
    set node_driving_pin [lindex $pin_drivers 0]

    # 查找第 1 步映射中的节点上的时钟,然后返回这些时钟
    return $nodes_with_clocks($node_driving_pin)
}