Quartus II Tcl Example: Arbitrary Paths Timing Reporting
The list_path and report_timing Tcl commands are very powerful, but they have some limitations. Path endpoints must be clocks, pins, or registers. Also, those commands do not report every combinational path between the endpoints. This advanced script example supports reporting timing on arbitrary paths in your design (including combinational endpoints), and reports all combinational paths between endpoints. The script uses an iterative search algorithm to find paths. The algorithm stops at pins and registers to prevent excessive run times.
You can specify node names, wildcards, or timegroup names for the source and destination. Timegroup exclusions are not supported by this script; a warning is displayed if you specify a timegroup that contains exclusions for the endpoints, and the exclusions are ignored.
You can direct the output of the script to a Comma Separated Value (.csv) file. The default file name is p2p_timing.csv. In addition, you can direct the output of the script to a panel in your project's timing report. The default panel name is Point-to-point Timing.
Assuming you save the script in a file named p2p_timing.tcl, you can run it with the following command:
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>]
If you want to direct the output to a file different than the default file name, you must specify both the -write_file and -file <output file name> options. If you want to direct the output to a report panel different than the default report panel name, you must specify both the -write_panel and -panel <report panel name> options.
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"]
##############################################################
#
# Returns a list that of node names and corresponding node IDs
# for design names that match the pattern argument.
# Any pattern that doesn't match names in the design has an
# empty list returned
# Example: Pass in "reset" and get { reset 3 } back
#
##############################################################
proc get_node_ids { pattern } {
array set name_to_node [list]
if { [string equal "" $pattern] } {
return [list]
}
# Is the pattern actually the name of a timegroup?
# If it is, then the script recursively gets the members of
# the timegroup.
set members [get_all_global_assignments -name TIMEGROUP_MEMBER \
-section_id $pattern]
# If there are any members of the collection,
# the pattern is a timegroup
if { 0 < [get_collection_size $members]} {
# Warn if there are exclusions, because the script
# skips exclusions
if {0 < [get_collection_size [get_all_global_assignments \
-name TIMEGROUP_EXCLUSION \
-section_id $pattern]] } {
post_message -type warning \
"Skipping exclusions in timegroup $pattern"
}
# Go through each item in the timegroup.
foreach_in_collection assignment $members {
# Each item in the collection is a list like this:
# {$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 {
# It is not a timegroup
# Iterate through all timing nodes in the design,
# checking whether the name matches the pattern specified
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]
}
##############################################################
#
# This procedure finds combinational paths between a source
# node and a list of destination nodes. It returns a list of
# paths between the nodes. Each path is made up of a triplet
# of node ID, and interconnect delay and cell delay from the
# previous node.
# The procedure stops traversing the netlist at a register
# or pin, so it does not find paths that go through registers.
#
##############################################################
proc find_combinational_paths_between { queue dest_nodes} {
set num_iterations 0
set paths [list]
while {0 < [llength $queue]} {
# Report on the progress of the loop every thousand
# iterations
incr num_iterations
if { 1000 == $num_iterations } {
set num_iterations 0
post_message "Checking [llength $queue] paths."
}
# Pop the first path from the queue.
# The first time the procedure is called, the queue
# is just one number, the source node.
set path [lindex $queue 0]
set queue [lrange $queue 1 end]
# Get the last node in the path, then in the foreach loop
# get fanout from that node
set last_triplet_in_path [lindex $path end]
set last_node_in_path [lindex $last_triplet_in_path 0]
# Extract just the node IDs in the current path.
# This is used later to ensure loops are not traversed.
set nodes_in_path [collapse_triplets_to_node_list $path]
# Get all fanouts of the last node in this path and make
# new paths with them to push on the queue.
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] } {
# If this node in the path is in the list of
# destination nodes, there is a path.
# Add it to the list of paths between the nodes
set new_path $path
lappend new_path $n
lappend paths $new_path
}
if { -1 == [lsearch $nodes_in_path $node_id] } {
# If this node in the path is not in the path
# already, this is not a loop. Push it on the
# queue if it is a combinational or clock node.
# The path is not pushed on if this node is a
# register or pin.
# Pushing a new path on the queue like this,
# even though this node in the path might match
# an end node, ensures the longest possible
# paths are found.
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
}
##############################################################
#
# Adds two delay numbers and returns the result.
# Delay numbers are in the form "value units" where units
# may be nanoseconds (ns) or picoseconds (ps), and value may
# be x{1,3} if the units are picoseconds, or x+.y{1,3} if the
# units are nanoseconds. This procedure normalizes delays to
# nanoseconds and adds the values.
# Example: 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"
}
# Convert everything to nanoseconds if required
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 }]]
}
# Now the units are equal, and nanoseconds.
# Just add the numbers together.
set sum_value [format "%.3f" [expr { $a_value + $b_value }]]
return "$sum_value ns"
}
##############################################################
#
# Formats and prints the node names in the path with the delays
# between the nodes.
#
##############################################################
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]
# Print the delays first
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
# Recurse on the rest of the path
if { 1 < [llength $path] } {
print_path_delays [lrange $path 1 end] other
}
}
##############################################################
#
# Sums the IC and cell delays on the specified path and
# returns a list with total interconnect delay and total cell
# delay.
#
##############################################################
proc end_to_end_delay { path } {
set ic_total "0.000 ns"
set cell_total "0.000 ns"
# This goes through nodes 1 to end in the path because the
# first node in the path is the source and each node in the
# path contains the delays from the node preceding it. The
# source has no node preceding it, so it has no delays.
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]
}
##############################################################
#
# Ensures the specified source and destinations exist in the
# design, finds the combinational paths between them, and
# prints the paths.
#
##############################################################
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
# Ensure the named nodes exist
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 they do, find paths.
if { $nodes_exist } {
# Get the list of destination node ids
set dest_node_ids [list]
foreach d [array names dests] {
lappend dest_node_ids $dests($d)
}
# Walk through all the from nodes
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 out the path
print_path_delays $path
# Sum the interconnect and cell delays and
# print them out under the 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]]
# There are two calls to print_accumulated_data
# here, one to print the sums of the interconnect
# and cell delays, and one to generate a blank
# line in the output.
print_accumulated_data
print_accumulated_data
}
}
}
}
}
##############################################################
#
# A path is made up of triplets of information - node id,
# interconnect delay, and cell delay. This procedure extracts
# the node id from each triplet in order and returns a list
# of the node ids
#
##############################################################
proc collapse_triplets_to_node_list { l } {
set to_return [list]
foreach triplet $l {
lappend to_return [lindex $triplet 0]
}
return $to_return
}
##############################################################
#
# Concatenates information to a global variable in preparation
# for it being printed out.
#
##############################################################
proc accumulate_data { data } {
global accum
set accum [concat $accum $data]
}
##############################################################
#
# Print out the accumulated data.
# It is printed to standard out and optionally to a file in
# CSV format if the file handle exists, and optionally to a
# report panel if the report panel exists (not a value of -1)
#
##############################################################
proc print_accumulated_data {} {
global accum fh panel_id
puts [join $accum ","]
# Write it out to a file?
if { [info exists fh] } {
puts $fh [join $accum ","]
}
# Add it to the report panel?
if { -1 != $panel_id } {
# If the report panel row doesn't have 4 items
# in it, pad it to 4.
while { 4 > [llength $accum] } {
lappend accum [list]
}
add_row_to_table -id $panel_id $accum
}
# Clear the information in the global variable.
set accum [list]
}
##############################################################
# ############################################################
# #
# # End of procedures, beginning of script
# #
# ############################################################
##############################################################
# Global variables to hold data for printing, and the panel
# ID for an optional report panel
set accum [list]
set panel_id -1
if { [string equal "" $opts(project)] } {
# Print usage options if the script is called without
# arguments
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
# Try to create the timing netlist. This command would fail
# if quartus_fit has not yet been run, for example.
if { [catch { create_timing_netlist } msg ] } {
post_message -type error $msg
} else {
# Prepare to write the output to a file if required
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)"
# Add some introductory information to the output 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"
}
}
# Prepare to write the output to a report panel if required
if { 1 == $opts(write_panel) } {
# Load the report, delete the panel if it exists already,
# create a new panel, and add the heading row.
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)
# close the output file if necessary
if { [info exists fh] } {
close $fh
}
# Save the report panel if necessary
if { -1 != $panel_id } {
save_report_database
unload_report
}
}
project_close
}
Design Examples Disclaimer
These design examples may only be used within Altera Corporation devices and remain the property of Altera. They are being provided on an “as-is” basis and as an accommodation; therefore, all warranties, representations, or guarantees of any kind (whether express, implied, or statutory) including, without limitation, warranties of merchantability, non-infringement, or fitness for a particular purpose, are specifically disclaimed. Altera expressly does not recommend, suggest, or require that these examples be used in combination with any other product not provided by Altera.
|