list_path 和 report_timing Tcl 命令非常强大,但都存在一定的限制。路径端点必须是时钟、引脚或寄存器。此外,这些命令无法报告端点之间的每个组合路径。此高级脚本示例可报告设计中任意路径(包括组合端点)的时序,并可报告端点之间的所有组合路径。此脚本使用迭代搜索算法查找路径。算法在引脚和寄存器上停止,防止运行时间过长。
可以为源节点和目标节点指定节点名称、通配符或时序组名称。此脚本不支持时序组异常;如果指定的时序组包含端点异常并且忽略了异常,将会显示警告。
可以将脚本的输出定向到逗号分隔值 (.csv) 文件。默认文件名为 p2p_timing.csv。此外,可以将脚本的输出定向到项目时序报告中的面板。默认面板名称为“点到点时序”。
quartus_tan -t p2p_timing.tcl -project <project name> -from <node name|wildcard|timegroup name> -to <node name|wildcard|timegroup name> [-write_file] [-file <output file name>] [-write_panel] [-panel <report panel name>]
如果要将输出定向到与默认文件名不同的文件,必须同时指定 -write_file 和-file <output file name> 选项。如果要将输出定向到与默认报告面板名称不同的报告面板,必须同时指定 -write_panel 和 -panel <report panel name> 选项。
将以下 Tcl 命令复制到一个文件,并将其命名为 p2p_timing.tcl。
package require cmdline load_package advanced_timing load_package report global quartus variable ::argv0 $::quartus(args) set options { \ { "from.arg" "" "Source node name" } \ { "to.arg" "" "Destination node name" } \ { "project.arg" "" "Project name" } \ { "file.arg" "p2p_timing.csv" "Output csv file name" } \ { "write_file" "" "Write the output to a file" } \ { "panel.arg" "Point-to-point Timing" "Report panel name" } \ { "write_panel" "" "Write the output to a report panel" } \ } array set opts [::cmdline::getoptions ::argv0 $options "Bad option"] ############################################################## # # 对与模式参数匹配的设计名称返回 # 节点名称和相应节点 ID 的列表。 # 对于与设计中的名称不匹配的任何模式,返回 # 一个空列表 # 示例:传入 "reset" 并再次获取 { reset 3 } # ############################################################## proc get_node_ids { pattern } { array set name_to_node [list] if { [string equal "" $pattern] } { return [list] } # 模式实际上是否为时序组的名称? # 如果是,脚本将以递归方式获取 # 时序组的成员。 set members [get_all_global_assignments -name TIMEGROUP_MEMBER -section_id $pattern] # 如果集合中有任何成员, # 表明模式是时序组 if { 0 < [get_collection_size $members]} { # 如果有异常,将发出警告,因为脚本 # 会跳过异常 if {0 < [get_collection_size [get_all_global_assignments -name TIMEGROUP_EXCLUSION -section_id $pattern]] } { post_message -type warning "Skipping exclusions in timegroup $pattern" } # 遍历时序组中的每个项。 foreach_in_collection assignment $members { # 集合中的每个项如以下列表所示: # {$pattern} {TIMEGROUP_MEMBER} {node/real pattern} array set sub_collection_names [get_node_ids [lindex $assignment 2]] foreach node_name [array names sub_collection_names] { set name_to_node($node_name) $sub_collection_names($node_name) } } } else { # 它不是时序组 # 对设计中的所有时序节点进行迭代, # 检查名称是否与指定模式匹配 foreach_in_collection node_id [get_timing_nodes -type all] { set node_name [get_timing_node_info -info name $node_id] if { [string match [escape_brackets $pattern] $node_name] } { set name_to_node($node_name) $node_id } } } return [array get name_to_node] } ############################################################## # # 此程序查找一个源节点与 # 一组目标节点之间的组合路径。它返回节点之间的 # 路径列表。每个路径都由 # 节点 ID、上一个节点的互连延迟和单元延迟 # 组成的三元组构成。 # 程序在寄存器或引脚上停止遍历网表, # 因此找不到通过寄存器的路径。 # ############################################################## proc find_combinational_paths_between {queue dest_nodes} { set num_iterations 0 set paths [list] while {0 < [llength $queue]} { # 每进行一千次迭代,报告一次循环进度 # incr num_iterations if { 1000 == $num_iterations } { set num_iterations 0 post_message "Checking [llength $queue] paths." } # 弹出队列中的第一个路径。 # 第一次调用程序时,队列中 # 仅有一项,即源节点。 set path [lindex $queue 0] set queue [lrange $queue 1 end] # 获取路径中的最后一个节点,然后在 foreach 循环中 # 获取此节点的扇出 set last_triplet_in_path [lindex $path end] set last_node_in_path [lindex $last_triplet_in_path 0] # 仅提取当前路径中的节点 ID。 # 稍后使用这些节点 ID 确保没有对循环进行遍历。 set nodes_in_path [collapse_triplets_to_node_list $path] # 获取此路径中最后一个节点的所有扇出, # 并使用这些扇出生成新的路径,从而将队列向前推进。 foreach n [get_timing_node_fanout $last_node_in_path] { foreach { node_id ic_delay cell_delay } $n { break } if { -1 != [lsearch $dest_nodes $node_id] } { # 如果路径中的此节点包含在 # 目标节点列表中,表明存在路径。 # 将其添加到节点间路径的列表中 set new_path $path lappend new_path $n lappend paths $new_path } if { -1 == [lsearch $nodes_in_path $node_id] } { # 如果路径中的此节点不再处于此路径中, # 表明它不是循环。如果它是组合节点 # 或时钟节点,将它添加到队列中,将队列向前推进。 # 如果此节点是寄存器或引脚,则不添加 # 路径。 以这种方式在队列中添加新路径, # 即使路径中的此节点可能与 # 一个端点节点匹配,也能确保找到 # 最长的路径。 set node_type [get_timing_node_info -info type $node_id] switch -exact -- $node_type { comb - clk { set next_path $path lappend next_path $n lappend queue $next_path } default { } } } } } return $paths } ############################################################## # # 添加两个延迟数,然后返回结果。 # 延迟数采用“值单位”的形式,其中单位 # 可为纳秒 (ns) 或皮秒 (ps),值可为 # x{1,3}(如果单位是皮秒)或 x+.y{1,3}(如果 # 单位是纳秒)。此程序将延迟归一化为 # 纳秒,并添加值。 # 示例:add_delays "1.234 ns" "56 ps" # ############################################################## proc add_delays { a b } { if { ![regexp {^([\d\.]+)\s+([np]s)$} $a match a_value a_unit] } { post_message -type error "Couldn't determine parts of time: $a" } if { ![regexp {^([\d\.]+)\s+([np]s)$} $b match b_value b_unit] } { post_message -type error "Couldn't determine parts of time: $b" } # 必要时将所有延迟转换为纳秒 if { [string equal -nocase ps $a_unit] } { set a_value_ps [format "%.3f" $a_value] set a_value [format "%.3f" [expr { $a_value_ps / 1000 }]] } if { [string equal -nocase ps $b_unit] } { set b_value_ps [format "%.3f" $b_value] set b_value [format "%.3f" [expr { $b_value_ps / 1000 }]] } # 现在单位均为纳秒。 # 将数字相加。 set sum_value [format "%.3f" [expr { $a_value + $b_value }]] return "$sum_value ns" } ############################################################## # # 如果路径中的节点之间有延迟,对这些节点的名称进行 # 格式化和打印。 # ############################################################## proc print_path_delays { path {iteration first}} { set source_triplet [lindex $path 0] set source_node [lindex $source_triplet 0] set source_node_name [get_timing_node_info -info name $source_node] set source_node_loc [get_timing_node_info -info location $source_node] # 先打印延迟 if { [string equal "first" $iteration] } { accumulate_data [list "IC(0.000 ns)" "CELL(0.000 ns)"] } else { set ic_delay [lindex $source_triplet 1] set cell_delay [lindex $source_triplet 2] accumulate_data [list "IC($ic_delay)" "CELL($cell_delay)"] } accumulate_data [list $source_node_loc $source_node_name] print_accumulated_data # 对路径的其余部分进行递归 if { 1 < [llength $path] } { print_path_delays [lrange $path 1 end] other } } ############################################################## # # 将指定路径上的互连延迟和单元延迟相加,然后 # 返回总互连延迟和总单元延迟的 # 列表。 # ############################################################## proc end_to_end_delay { path } { set ic_total "0.000 ns" set cell_total "0.000 ns" # 由于路径中的第一个节点是源节点,路径中的 # 每个节点都包含上一个节点的延迟,因此需要 # 从节点 1 进行到路径中的最后一个节点。源 # 节点前面没有任何节点,因此不包含延迟。 foreach n [lrange $path 1 end] { foreach { node_id ic_delay cell_delay } $n { break } set ic_total [add_delays $ic_total $ic_delay] set cell_total [add_delays $cell_total $cell_delay] } return [list $ic_total $cell_total] } ############################################################## # # 确保设计中有指定源节点和目标 # 节点,找到它们之间的组合路径,然后 # 打印路径。 # ############################################################## proc find_paths_and_display { source dest } { array set sources [get_node_ids $source] array set dests [get_node_ids $dest] set nodes_exist 1 # 确保存在指定节点 if { 0 == [llength [array get sources]] } { set nodes_exist 0 post_message -type error "No nodes matching $source were found in your design." } if { 0 == [llength [array get dests]] } { set nodes_exist 0 post_message -type error "No nodes matching $dest were found in your design." } # 如果存在,找到路径。 if { $nodes_exist } { # 获取目标节点 ID 列表 set dest_node_ids [list] foreach d [array names dests] { lappend dest_node_ids $dests($d) } # 遍历所有节点 foreach s [array names sources] { set paths [find_combinational_paths_between $sources($s) $dest_node_ids] if { 0 == [llength $paths] } { post_message "No combinational path exists from $s to $dest" } else { foreach path $paths { # 打印路径 print_path_delays $path # 将互连延迟和单元延迟相加,然后 # 将其打印到路径下。 foreach {total_ic_delay total_cell_delay } [end_to_end_delay $path] { break } accumulate_data [list $total_ic_delay $total_cell_delay] accumulate_data [list [add_delays $total_ic_delay $total_cell_delay]] # 此处调用了两次 print_accumulated_data # 一次是为了打印互连延迟与 # 单元延迟的和,另一次是为了在输出中 # 生成空行。 print_accumulated_data print_accumulated_data } } } } } ############################################################## # # 路径由信息三元组构成 - 节点 ID、 # 互连延迟和单元延迟。此程序按顺序 # 提取每个三元组中的节点 ID,然后返回 # 节点 ID 列表 # ############################################################## proc collapse_triplets_to_node_list { l } { set to_return [list] foreach triplet $l { lappend to_return [lindex $triplet 0] } return $to_return } ############################################################## # # 将信息连接到准备中的全局变量, # 以供打印。 # ############################################################## proc accumulate_data { data } { global accum set accum [concat $accum $data] } ############################################################## # # 打印累加数据。 # 将其打印到标准输出,如果已存在文件句柄, # 可以 CSV 格式打印到文件,如果已存在 # 报告面板(值不是 -1),可以打印到报告面板 # ############################################################## proc print_accumulated_data {} { global accum fh panel_id puts [join $accum ","] # 将其写入文件? if { [info exists fh] } { puts $fh [join $accum ","] } # 将其添加到报告面板? if { -1 != $panel_id } { # 如果报告面板行中的项数不足 4 个, # 填充成 4 项。 while { 4 > [llength $accum] } { lappend accum [list] } add_row_to_table -id $panel_id $accum } # 清除全局变量中的信息。 set accum [list] } ############################################################## ############################################################## # # 程序结束,脚本开始 # ############################################################## ############################################################## # # 用于容纳待打印数据的全局变量,以及 # 可选报告面板的面板 ID set accum [list] set panel_id -1 if { [string equal "" $opts(project)] } { # 如果在未使用参数的情况下调用了脚本,打印 usage # 选项 puts [::cmdline::usage $options] } elseif { [string equal "" $opts(project)] } { post_message -type error "Specify a project with the -project option." } elseif { ! [project_exists $opts(project)] } { post_message -type error "The project $opts(project) does not exist in this directory." } elseif { [string equal "" $opts(from)] } { post_message -type error "Specify a name or wildcard pattern with the -from option." } elseif { [string equal "" $opts(to)] } { post_message -type error "Specify a name or wildcard pattern with the -to option." } else { set cur_revision [get_current_revision $opts(project)] project_open $opts(project) -revision $cur_revision # 尝试创建时序网表。在尚未运行 quartus_fit 等 # 情况下,此命令将会失败。 if { [catch { create_timing_netlist } msg ] } { post_message -type error $msg } else { # 必要时,准备将输出写入文件 if { 1 == $opts(write_file) } { if { [catch {open $opts(file) w} fh] } { post_message -type error "Couldn't open $opts(write_file): $fh" unset fh } else { post_message "Writing output to $opts(file)" # 在输出文件中添加一些介绍信息 puts $fh "Report of paths from $opts(from) to $opts(to)" puts $fh "Generated on [clock format [clock seconds]]" puts $fh "" puts $fh "IC delay,Cell delay,Node location,Node name" } } # 必要时,准备将输出写入报告面板 if { 1 == $opts(write_panel) } { # 加载报告,删除已有面板, # 创建一个新面板,并添加标题行。 load_report set panel_id [get_report_panel_id "Timing Analyzer||$opts(panel)"] if { -1 != $panel_id } { delete_report_panel -id $panel_id } set panel_id [create_report_panel -table "Timing Analyzer||$opts(panel)"] add_row_to_table -id $panel_id [list "IC delay" "Cell delay" "Node location" "Node name"] } find_paths_and_display $opts(from) $opts(to) # 必要时关闭输出文件 if { [info exists fh] } { close $fh } # 必要时保存报告面板 if { -1 != $panel_id } { save_report_database unload_report } } project_close }