OpenSTA/graph/Graph.tcl

411 lines
12 KiB
Tcl

# OpenSTA, Static Timing Analyzer
# Copyright (c) 2024, Parallax Software, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Graph utilities
namespace eval sta {
define_cmd_args "report_edges" {[-from from_pin] [-to to_pin]}
proc report_edges { args } {
parse_key_args "report_edges" args keys {-from -to} flags {}
check_argc_eq0 "report_edges" $args
if { [info exists keys(-from)] && [info exists keys(-to)] } {
set from_pin [get_port_pin_error "from_pin" $keys(-from)]
set to_pin [get_port_pin_error "to_pin" $keys(-to)]
foreach from_vertex [$from_pin vertices] {
foreach to_vertex [$to_pin vertices] {
report_edges_between_ $from_vertex $to_vertex
}
}
} elseif [info exists keys(-from)] {
set from_pin [get_port_pin_error "from_pin" $keys(-from)]
foreach from_vertex [$from_pin vertices] {
report_edges_ $from_vertex out_edge_iterator \
vertex_port_name vertex_path_name
}
} elseif [info exists keys(-to)] {
set to_pin [get_port_pin_error "to_pin" $keys(-to)]
foreach to_vertex [$to_pin vertices] {
report_edges_ $to_vertex in_edge_iterator \
vertex_path_name vertex_port_name
}
}
}
proc report_edges_between_ { from_vertex to_vertex } {
set iter [$from_vertex out_edge_iterator]
while {[$iter has_next]} {
set edge [$iter next]
if { [$edge to] == $to_vertex } {
if { [$edge role] == "wire" } {
report_edge_ $edge vertex_path_name vertex_path_name
} else {
report_edge_ $edge vertex_port_name vertex_port_name
}
}
}
$iter finish
}
proc report_edges_ { vertex iter_proc wire_from_name_proc wire_to_name_proc } {
# First report edges internal to the device.
set device_header 0
set iter [$vertex $iter_proc]
while {[$iter has_next]} {
set edge [$iter next]
if { [$edge role] != "wire" } {
if { !$device_header } {
set pin [$vertex pin]
if { ![$pin is_top_level_port] } {
set inst [$pin instance]
}
set device_header 1
}
report_edge_ $edge vertex_port_name vertex_port_name
}
}
$iter finish
# Then wires.
set iter [$vertex $iter_proc]
while {[$iter has_next]} {
set edge [$iter next]
if { [$edge role] == "wire" } {
report_edge_ $edge $wire_from_name_proc $wire_to_name_proc
}
}
$iter finish
}
proc report_edge_ { edge vertex_from_name_proc vertex_to_name_proc } {
global sta_report_default_digits
set latch_enable [$edge latch_d_to_q_en]
if { $latch_enable != "" } {
set latch_enable " enable $latch_enable"
}
report_line "[$vertex_from_name_proc [$edge from]] -> [$vertex_to_name_proc [$edge to]] [$edge role]$latch_enable"
set disables [edge_disable_reason $edge]
if { $disables != "" } {
report_line " Disabled by $disables"
}
set cond [$edge cond]
if { $cond != "" } {
report_line " Condition: $cond"
}
set mode_name [$edge mode_name]
if { $mode_name != "" } {
report_line " Mode: $mode_name [$edge mode_value]"
}
foreach arc [$edge timing_arcs] {
set delays [$edge arc_delay_strings $arc $sta_report_default_digits]
set delays_fmt [format_delays $delays]
set disable_reason ""
if { [timing_arc_disabled $edge $arc] } {
set disable_reason " disabled"
}
report_line " [$arc from_edge] -> [$arc to_edge] $delays_fmt$disable_reason"
}
}
# Separate list elements with colons.
proc format_times { values digits } {
set result ""
foreach value $values {
if { $result != "" } {
append result ":"
}
append result [format_time $value $digits]
}
return $result
}
# Separate delay list elements with colons.
proc format_delays { values } {
set result ""
foreach value $values {
if { $result != "" } {
append result ":"
}
append result $value
}
return $result
}
proc edge_disable_reason { edge } {
set disables ""
if [$edge is_disabled_constraint] {
append disables "constraint"
}
if [$edge is_disabled_constant] {
if { $disables != "" } { append disables ", " }
append disables "constant"
}
if [$edge is_disabled_cond_default] {
if { $disables != "" } { append disables ", " }
append disables "cond_default"
}
if [$edge is_disabled_loop] {
if { $disables != "" } { append disables ", " }
append disables "loop"
}
if [$edge is_disabled_bidirect_inst_path] {
if { $disables != "" } { append disables ", " }
append disables "bidirect instance path"
}
if [$edge is_disabled_bidirect_net_path] {
if { $disables != "" } { append disables ", " }
append disables "bidirect net path"
}
if { [$edge is_disabled_preset_clear] } {
if { $disables != "" } { append disables ", " }
append disables "timing_enable_preset_clear_arcs"
}
return $disables
}
################################################################
define_cmd_args "report_constant" {pin|instance|net}
proc report_constant { obj } {
parse_inst_port_pin_net_arg $obj insts pins nets
foreach pin $pins {
report_pin_constant $pin
}
foreach inst $insts {
set pin_iter [$inst pin_iterator]
while {[$pin_iter has_next]} {
set pin [$pin_iter next]
report_pin_constant $pin
}
$pin_iter finish
}
foreach net $nets {
set pin_iter [$net pin_iterator]
while {[$pin_iter has_next]} {
set pin [$pin_iter next]
report_pin_constant $pin
}
$pin_iter finish
}
}
proc report_pin_constant { pin } {
set sim_value [pin_sim_logic_value $pin]
set case_value [pin_case_logic_value $pin]
if { $case_value != "X" } {
set case " case=$case_value"
} else {
set case ""
}
set logic_value [pin_logic_value $pin]
if { $logic_value != "X" } {
set logic " logic=$logic_value"
} else {
set logic ""
}
report_line "[pin_property $pin lib_pin_name] $sim_value$case$logic"
}
################################################################
proc_redirect report_disabled_edges {
foreach edge [disabled_edges_sorted] {
if { [$edge role] == "wire" } {
set from_pin_name [get_full_name [[$edge from] pin]]
set to_pin_name [get_full_name [[$edge to] pin]]
report_line "$from_pin_name $to_pin_name [edge_disable_reason_verbose $edge]"
} else {
set from_pin [[$edge from] pin]
set to_pin [[$edge to] pin]
set inst_name [get_full_name [$from_pin instance]]
set from_port_name [get_name [$from_pin port]]
set to_port_name [get_name [$to_pin port]]
set cond [$edge cond]
if { $cond != "" } {
set when " when: $cond"
} else {
set when ""
}
report_line "$inst_name $from_port_name $to_port_name$when [edge_disable_reason_verbose $edge]"
}
}
}
proc edge_disable_reason_verbose { edge } {
set disables ""
if { [$edge is_disabled_constraint] } {
append disables "constraint"
}
if { [$edge is_disabled_constant] } {
if { $disables != "" } { append disables ", " }
append disables "constant"
set sense [$edge sim_timing_sense]
if { $sense == "positive_unate" || $sense == "negative_unate" } {
append disables " $sense"
}
set const_pins [$edge disabled_constant_pins]
foreach pin [sort_by_full_name $const_pins] {
set port_name [pin_property $pin lib_pin_name]
set value [pin_sim_logic_value $pin]
append disables " $port_name=$value"
}
}
if { [$edge is_disabled_cond_default] } {
if { $disables != "" } { append disables ", " }
append disables "cond_default"
}
if { [$edge is_disabled_loop] } {
if { $disables != "" } { append disables ", " }
append disables "loop"
}
if { [$edge is_disabled_preset_clear] } {
if { $disables != "" } { append disables ", " }
append disables "timing_enable_preset_clear_arcs"
}
return $disables
}
################################################################
define_cmd_args "report_slews" {[-corner corner] pin}
proc report_slews { args } {
global sta_report_default_digits
parse_key_args "report_slews" args keys {-corner} flags {}
check_argc_eq1 "report_slews" $args
set corner [parse_corner_or_all keys]
set pin [get_port_pin_error "pin" [lindex $args 0]]
set digits $sta_report_default_digits
foreach vertex [$pin vertices] {
if { $corner == "NULL" } {
report_line "[vertex_path_name $vertex] [rise_short_name] [format_time [$vertex slew rise min] $digits]:[format_time [$vertex slew rise max] $digits] [fall_short_name] [format_time [$vertex slew fall min] $digits]:[format_time [$vertex slew fall max] $digits]"
} else {
report_line "[vertex_path_name $vertex] [rise_short_name] [format_time [$vertex slew_corner rise $corner min] $digits]:[format_time [$vertex slew_corner rise $corner max] $digits] [fall_short_name] [format_time [$vertex slew_corner fall $corner min] $digits]:[format_time [$vertex slew_corner fall $corner max] $digits]"
}
}
}
proc vertex_path_name { vertex } {
set pin [$vertex pin]
set pin_name [get_full_name $pin]
return [vertex_name_ $vertex $pin $pin_name]
}
proc vertex_port_name { vertex } {
set pin [$vertex pin]
set pin_name [pin_property $pin lib_pin_name]
return [vertex_name_ $vertex $pin $pin_name]
}
# Append driver/load for bidirect pin vertices.
proc vertex_name_ { vertex pin pin_name } {
if { [pin_direction $pin] == "bidirect" } {
if [$vertex is_bidirect_driver] {
return "$pin_name driver"
} else {
return "$pin_name load "
}
} else {
return $pin_name
}
}
# Return a list of hierarchical pins crossed by a graph edge.
proc hier_pins_crossed_by_edge { edge } {
set from_pins [hier_pins_above [[$edge from] pin]]
set to_pins [hier_pins_above [[$edge to] pin]]
foreach p $to_pins { report_line [$p path_name] }
while { [llength $from_pins] > 0 \
&& [llength $to_pins] > 0 \
&& [lindex $from_pins 0] == [lindex $to_pins 0] } {
set from_pins [lrange $from_pins 1 end]
set to_pins [lrange $to_pins 1 end]
}
return [concat $from_pins $to_pins]
}
proc hier_pins_above { pin } {
set pins_above {}
set inst [$pin instance]
set net [$pin net]
set parent [$inst parent]
while { $parent != "NULL" } {
set found 0
set parent_pin_iter [$parent pin_iterator]
while {[$parent_pin_iter has_next]} {
set parent_pin [$parent_pin_iter next]
if {[$parent_pin net] == $net} {
set pins_above [concat [list $parent_pin] $pins_above]
set found 1
break
}
}
$parent_pin_iter finish
if { !$found} {
break
}
set parent [$parent parent]
}
return $pins_above
}
proc report_level { pin } {
set pin1 [get_port_pin_error "pin" $pin]
foreach vertex [$pin1 vertices] {
if { $vertex != "NULL" } {
report_line "[vertex_path_name $vertex] level = [$vertex level]"
}
}
}
# Show how many pins are at each level from an input.
proc report_level_distribution {} {
set max_level 0
set iter [vertex_iterator]
while {[$iter has_next]} {
set vertex [$iter next]
set level [$vertex level]
if { $level > $max_level } {
set max_level $level
}
if [info exists count($level)] {
incr count($level)
} else {
set count($level) 1
}
}
$iter finish
report_line "level pin count"
for { set level 0 } { $level < $max_level } { incr level } {
report_line " $level $count($level)"
}
}
# sta namespace end
}