针对特定的设计,就编译时间而言,我们要分析时间都消耗在哪些环节从而有针对性的缩短编译时间。通常,时间可能花费在加载约束上,也可能花费在某个子步骤如布局。有时面对的情况是同一设计在不同Vivado版本上运行时间出现较大差异。
profiler
profiler add *
profiler start
open_checkpoint top_routed.dcp
profiler stop
profiler summary
profiler summary -log profiler.log
profiler summary -log profiler.csv -csv
####################################################################################################
# HEADER_BEGIN
# COPYRIGHT NOTICE
# Copyright 2001-2018 Xilinx Inc. All Rights Reserved.
# http://www.xilinx.com/support
# HEADER_END
####################################################################################################
########################################################################################
##
## Company: Xilinx, Inc.
## Created by: David Pefourque
##
## Version: 2018.09.11 (Customer Release)
## Tool Version: Vivado 2014.1
## Description: This package provides a simple profiler for Vivado commands
##
########################################################################################
########################################################################################
## 2018.09.11 - Fixed issue with the number of objects caped at 501 with Vivado 2018.3
## 2018.09.07 - Added -summary to method 'summary'
## 2018.09.05 - Added -hms/-format/-ignored_commands/-profiled_commands/-hide_ignored
## to method 'summary'
## - Improved command line options handling
## 2018.04.03 - Fixed stack level for method 'time'
## 2018.03.24 - Improved the log file CSV format
## 2018.02.10 - Added the top 10 largest collections of objects in the log file
## 2018.01.29 - Added the number of returned objects in the "Top 50 Runtimes" summary
## 2017.11.21 - Modified output format for method 'summary' with -csv:
## the detailed summary is now reported as CSV
## 2017.11.17 - Switch tables generation to prettyTable
## - Added option -csv to method 'summary'
## 2016.07.29 - Added method 'configure'
## - Added tuncation of long command lines inside log file
## (improved performance and better support for very large XDC)
## 2014.07.03 - Fixed issue with clock formating that prevented the script from running
## under Windows
## 2014.05.13 - Updated package requirement to Vivado 2014.1
## 2013.10.03 - Changed version format to 2013.10.03 to be compatible with 'package' command
## - Added version number to namespace
## 09/16/2013 - Updated 'docstring' to support meta-comment 'Categories' for linter
## 03/29/2013 - Minor fix
## 03/26/2013 - Reformated the log file and added the top 50 worst runtimes
## - Renamed subcommand 'exec' to 'time'
## - Removed 'read_xdc' from the list of commands that contribute to the
## total runtime
## - Added subcommand 'version'
## - Added subcommand 'configure'
## - Added options -collection_display_limit & -src_info to subcommand 'start'
## - Modified the subcommand 'time' to accept the same command line arguments
## as the subcommand 'start'
## 03/21/2013 - Initial release
########################################################################################
# Profiler usage:
# profiler add * (-help for additional help)
# profiler start (-help for additional help)
# <execute code>
# profiler stop
# profiler summary (-help for additional help)
#
# OR
#
# profiler add * (-help for additional help)
# profiler time { ... }
if {[package provide Vivado] == {}} {return}
package require Vivado 1.2014.1
# Check if designutils app is already installed as it is needed for prettyTable
if {[lsearch -exact [::tclapp::list_apps] {xilinx::designutils}] == -1} {
uplevel #0 [list ::tclapp::install {designutils} ]
}
namespace eval ::tb {
namespace export profiler
}
proc ::tb::profiler { args } {
# Summary : Tcl profiler
# Argument Usage:
# args : sub-command. The supported sub-commands are: start | stop | summary | add | remove | reset | status
# Return Value:
# returns the status or an error code
# if {[catch {set res [uplevel [concat ::tb::profiler::profiler $args]]} errorstring]} {
# error " -E- the profiler failed with the following error: $errorstring"
# }
return [uplevel [concat ::tb::profiler::profiler $args]]
}
###########################################################################
##
## Package for profiling Tcl code
##
###########################################################################
#------------------------------------------------------------------------
# Namespace for the package
#------------------------------------------------------------------------
# Trick to silence the linter
eval [list namespace eval ::tb::profiler {
# if {1 || ![info exists ::tb::profiler::params]} {}
if {1} {
# Only reset the variables if they have not been set yet
variable version {2018.09.11 (C)}
variable cmdlist [list]
variable tmstart [list]
variable tmend [list]
variable params
variable db [list]
catch {unset params}
array set params [list mode {stopped} formatDelay {default} expandObjects 0 collectionResultDisplayLimit -1 ]
} else {
# Use method 'reset' to reset the parameters
variable version
variable cmdlist
variable tmstart
variable tmend
variable params
variable db
}
} ]
#------------------------------------------------------------------------
# ::tb::profiler::profiler
#------------------------------------------------------------------------
# Main function
#------------------------------------------------------------------------
proc ::tb::profiler::profiler { args } {
# Summary :
# Argument Usage:
# Return Value:
#-------------------------------------------------------
# Process command line arguments
#-------------------------------------------------------
set error 0
set show_help 0
set method [lshift args]
switch -exact -- $method {
dump {
return [eval [concat ::tb::profiler::dump] ]
}
? -
-h -
-help {
incr show_help
}
default {
return [eval [concat ::tb::profiler::do ${method} $args] ]
}
}
if {$show_help} {
# <-- HELP
puts ""
::tb::profiler::method:?
puts [format {
Description: Utility to profile Vivado commands
Example1:
profiler add *
profiler start -incr
<execute some Tcl code with Vivado commands>
profiler stop
profiler summary
profiler reset
Example2:
profiler add *
profiler time { <execute some Tcl code with Vivado commands> }
profiler summary
profiler reset
} ]
# HELP -->
return
}
}
#------------------------------------------------------------------------
# ::tb::profiler::lshift
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Stack function
#------------------------------------------------------------------------
proc ::tb::profiler::lshift {inputlist} {
# Summary :
# Argument Usage:
# Return Value:
upvar $inputlist argv
set arg [lindex $argv 0]
set argv [lrange $argv 1 end]
return $arg
}
#------------------------------------------------------------------------
# ::tb::profiler::lflatten
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Flatten a nested list
#------------------------------------------------------------------------
proc ::tb::profiler::lflatten {inputlist} {
# Summary :
# Argument Usage:
# Return Value:
while { $inputlist != [set inputlist [join $inputlist]] } { }
return $inputlist
}
#------------------------------------------------------------------------
# ::tb::profiler::lremove
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Remove element from a list
#------------------------------------------------------------------------
proc ::tb::profiler::lremove {_inputlist element} {
# Summary :
# Argument Usage:
# Return Value:
upvar 1 $_inputlist inputlist
set pos [lsearch -exact $inputlist $element]
set inputlist [lreplace $inputlist $pos $pos]
}
#------------------------------------------------------------------------
# ::tb::profiler::duration
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Convert a milisecond time into a HMS format
#------------------------------------------------------------------------
proc ::tb::profiler::duration { int_time } {
# Summary :
# Argument Usage:
# Return Value:
if {$int_time < 1000} {
# If less than 1000ms, return "<1s"
return {<1s}
}
# Convert miliseconds into seconds
set int_time [expr int($int_time / 1000)]
set timeList [list]
if {$int_time == 0} { return "0sec" }
foreach div {86400 3600 60 1} mod {0 24 60 60} name {day hr min sec} {
set n [expr {$int_time / $div}]
if {$mod > 0} {set n [expr {$n % $mod}]}
if {$n > 1} {
lappend timeList "${n} ${name}s"
} elseif {$n == 1} {
lappend timeList "${n} $name"
}
}
return [join $timeList { }]
}
#------------------------------------------------------------------------
# ::tb::profiler::loadstate
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Load profiler state
#------------------------------------------------------------------------
proc ::tb::profiler::loadstate { filename } {
# Summary :
# Argument Usage:
# Return Value:
if {[regexp {.gz$} $filename]} {
# gzip-ed file
set content {}
set FH [open "| zcat $filename" {r}]
while {![eof $FH]} {
gets $FH line
append content $line
if {[info complete $content]} {
if {[catch {eval $content} errorstring]} {
puts " -E- $errorstring"
}
set content {}
}
}
close $FH
} else {
if {[catch {source $filename} errorstring]} {
puts " -E- $errorstring"
}
}
puts " -I- finished loading file [file normalize $filename]"
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::savestate
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Save profiler state
#------------------------------------------------------------------------
proc ::tb::profiler::savestate { filename {expanded 0} } {
# Summary :
# Argument Usage:
# Return Value:
variable db
variable cmdlist
variable tmend
variable tmstart
variable params
set FH [open $filename {w}]
puts $FH "# To reload the saved state:"
puts $FH "# tb::profiler::loadstate <filename>"
puts $FH [format {array set ::tb::profiler::params {%s}} [array get params] ]
# Write out the $DB variable (1 entry per line)
puts $FH "set ::tb::profiler::db \[list\]"
foreach i $db {
lassign $i clk enter cmdline code result src_info
if {$expanded} {
# Expanded form, write the command name with its arguments
puts $FH [format {lappend ::tb::profiler::db [list {%s} {%s} {%s} {%s} {%s} {%s} ]} $clk $enter $cmdline $code $result $src_info]
} else {
# Only write the command name, not the arguments
puts $FH [format {lappend ::tb::profiler::db [list {%s} {%s} {%s} {%s} {%s} {%s} ]} $clk $enter [lindex $cmdline 0] $code $result $src_info]
}
}
puts $FH [format {set ::tb::profiler::cmdlist {%s}} $cmdlist]
puts $FH [format {set ::tb::profiler::tmstart {%s}} $tmstart]
puts $FH [format {set ::tb::profiler::tmend {%s}} $tmend]
close $FH
puts " -I- generated file [file normalize $filename]"
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::formatDelay
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Convert a milisecond time into a different format (HMS, seconds, ms)
#------------------------------------------------------------------------
proc ::tb::profiler::formatDelay { int_time } {
# Summary :
# Argument Usage:
# Return Value:
variable params
switch $params(formatDelay) {
hms {
# hours/minutes/seconds
return [duration [expr $int_time / 1000.0]]
}
ms {
# miliseconds
return [format {%.0fms} [expr $int_time / 1000.0]]
}
s -
sec {
# seconds
return [format {%.0fs} [expr $int_time / 1000000.0]]
}
default {
return [format {%.3fms} [expr $int_time / 1000.0]]
}
}
}
#------------------------------------------------------------------------
# ::tb::profiler::docstring
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Return the embedded help of a proc
#------------------------------------------------------------------------
proc ::tb::profiler::docstring {procname} {
# Summary :
# Argument Usage:
# Return Value:
if {[info proc $procname] ne $procname} { return }
# reports a proc's args and leading comments.
# Multiple documentation lines are allowed.
set res ""
# This comment should not appear in the docstring
foreach line [split [uplevel 1 [list info body $procname]] \n] {
if {[string trim $line] eq ""} continue
# Skip comments that have been added to support rdi::register_proc command
if {[regexp -nocase -- {^\s*#\s*(Summary|Argument Usage|Return Value|Categories)\s*\:} $line]} continue
if {![regexp {^\s*#(.+)} $line -> line]} break
lappend res [string trim $line]
}
join $res \n
}
#------------------------------------------------------------------------
# ::tb::profiler::do
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Dispatcher with methods
#------------------------------------------------------------------------
proc ::tb::profiler::do {args} {
# Summary :
# Argument Usage:
# Return Value:
if {[llength $args] == 0} {
# error " -E- wrong number of parameters: profiler <sub-command> \[<arguments>\]"
set method {?}
} else {
# The first argument is the method
set method [lshift args]
}
if {[info proc ::tb::profiler::method:${method}] == "::tb::profiler::method:${method}"} {
eval ::tb::profiler::method:${method} $args
} else {
# Search for a unique matching method among all the available methods
set match [list]
foreach procname [info proc ::tb::profiler::method:*] {
if {[string first $method [regsub {::tb::profiler::method:} $procname {}]] == 0} {
lappend match [regsub {::tb::profiler::method:} $procname {}]
}
}
switch [llength $match] {
0 {
error " -E- unknown sub-command $method"
}
1 {
set method $match
return [eval ::tb::profiler::method:${method} $args]
}
default {
error " -E- multiple sub-commands match '$method': $match"
}
}
}
}
#------------------------------------------------------------------------
# ::tb::profiler::method:?
#------------------------------------------------------------------------
# Usage: profiler ?
#------------------------------------------------------------------------
# Return all the available methods. The methods with no embedded help
# are not displayed (i.e hidden)
#------------------------------------------------------------------------
proc ::tb::profiler::method:? {args} {
# Summary :
# Argument Usage:
# Return Value:
# This help message
puts " Usage: profiler <sub-command> \[<arguments>\]"
puts " Where <sub-command> is:"
foreach procname [lsort [info proc ::tb::profiler::method:*]] {
regsub {::tb::profiler::method:} $procname {} method
set help [::tb::profiler::docstring $procname]
if {$help ne ""} {
puts " [format {%-12s%s- %s} $method \t $help]"
}
}
puts ""
}
#------------------------------------------------------------------------
# ::tb::profiler::enter
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Called before a profiled command is executed
#------------------------------------------------------------------------
proc ::tb::profiler::enter {cmd op} {
# Summary :
# Argument Usage:
# Return Value:
variable db
lappend db [list [clock microseconds] 1 $cmd]
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::leave1
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Called after a profiled command is executed
#------------------------------------------------------------------------
proc ::tb::profiler::leave1 {cmd code result op} {
# Summary :
# Argument Usage:
# Return Value:
variable db
variable params
if {$params(expandObjects)} {
# Save the list of return objects
lappend db [list [clock microseconds] 0 $cmd $code $result]
} else {
# Only save the number of return objects
lappend db [list [clock microseconds] 0 $cmd $code [list [format {%d objects} [llength $result]]] ]
}
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::leave2
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Verbose version of ::tb::profiler::leave1
# Save the source information inside the database
#------------------------------------------------------------------------
proc ::tb::profiler::leave2 {cmd code result op} {
# Summary :
# Argument Usage:
# Return Value:
variable db
variable params
# Create temp variable in case [current_design] does not exist
set src_info {}
catch { set src_info [get_property -quiet src_info [current_design -quiet]] }
if {$params(expandObjects)} {
# Save the list of return objects
lappend db [list [clock microseconds] 0 $cmd $code $result $src_info ]
} else {
# Only save the number of return objects
lappend db [list [clock microseconds] 0 $cmd $code [list [format {%d objects} [llength $result]]] $src_info]
}
# lappend db [list [clock microseconds] 0 $cmd $code $result [get_property src_info [current_design]] ]
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::trace_off
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Remove all 'trace' commands
#------------------------------------------------------------------------
proc ::tb::profiler::trace_off {args} {
# Summary :
# Argument Usage:
# Return Value:
variable cmdlist
foreach cmd $cmdlist {
catch { trace remove execution $cmd enter ::tb::profiler::enter }
catch { trace remove execution $cmd leave ::tb::profiler::leave }
}
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::trace_on
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Add all 'trace' commands
#------------------------------------------------------------------------
proc ::tb::profiler::trace_on {args} {
# Summary :
# Argument Usage:
# Return Value:
variable cmdlist
# For safety, tries to remove any existing 'trace' commands
::tb::profiler::trace_off
# Now adds 'trace' commands
foreach cmd $cmdlist {
catch { trace add execution $cmd enter ::tb::profiler::enter }
catch { trace add execution $cmd leave ::tb::profiler::leave }
}
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::dump
#------------------------------------------------------------------------
# Usage: profiler dump
#------------------------------------------------------------------------
# Dump profiler status
#------------------------------------------------------------------------
proc ::tb::profiler::dump {args} {
# Summary :
# Argument Usage:
# Return Value:
# Dump 'trace' information
::tb::profiler::trace_info
# Dump non-array variables
foreach var [lsort [info var ::tb::profiler::*]] {
if {![info exists $var]} { continue }
if {![array exists $var]} {
puts " $var: [subst $$var]"
}
}
# Dump array variables
foreach var [lsort [info var ::tb::profiler::*]] {
if {![info exists $var]} { continue }
if {[array exists $var]} {
parray $var
}
}
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::trace_info
#------------------------------------------------------------------------
# **INTERNAL**
#------------------------------------------------------------------------
# Dump the 'trace' information on each command
#------------------------------------------------------------------------
proc ::tb::profiler::trace_info {args} {
# Summary :
# Argument Usage:
# Return Value:
variable cmdlist
foreach cmd $cmdlist {
if {[catch { puts " $cmd:[trace info execution $cmd]" } errorstring]} {
puts " $cmd: <ERROR: $errorstring>"
}
}
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:version
#------------------------------------------------------------------------
# Usage: profiler version
#------------------------------------------------------------------------
# Return the version of the profiler
#------------------------------------------------------------------------
proc ::tb::profiler::method:version {args} {
# Summary :
# Argument Usage:
# Return Value:
# Version of the profiler
variable version
# puts " -I- Profiler version $version"
return -code ok "Profiler version $version"
}
#------------------------------------------------------------------------
# ::tb::profiler::method:add
#------------------------------------------------------------------------
# Usage: profiler add [<options>]
#------------------------------------------------------------------------
# Add Vivado command(s) to the profiler
#------------------------------------------------------------------------
proc ::tb::profiler::method:add {args} {
# Summary :
# Argument Usage:
# Return Value:
# Add Vivado command(s) to the profiler (-help)
variable cmdlist
variable params
if {$params(mode) == {started}} {
error " -E- cannot add command(s) when the profiler is running. Use 'profiler stop' to stop the profiler"
}
if {[llength $args] == 0} {
error " -E- no argument provided"
}
set error 0
set commands [list]
set force 0
set tmp_args [list]
set help 0
while {[llength $args]} {
set name [lshift args]
switch -regexp -- $name {
{^-force$} -
{^-f(o(r(ce?)?)?)?$} {
set force 1
}
{^-h(e(lp?)?)?$} {
set help 1
}
default {
if {[string match "-*" $name]} {
puts " -E- option '$name' is not a valid option."
incr error
} else {
lappend tmp_args $name
}
}
}
}
if {$error} {
error " -E- some error(s) happened. Cannot continue"
}
if {$help} {
puts [format {
Usage: profiler add
<pattern_of_commands>
[<pattern_of_commands>]
[-f|-force]
[-help|-h]
Description: Add commands to the profiler
Example:
profiler add *
profiler add get_*
profiler add -force *
} ]
# HELP -->
return {}
}
# Restore 'args'
set args $tmp_args
foreach pattern [::tb::profiler::lflatten $args] {
if {[string first {*} $pattern] != -1} {
# If the pattern contains an asterix '*' then the next 'foreach' loop
# should not generate some of the warning messages since the user
# just provided a pattern
set verbose 0
} else {
# A specific command name has been provided, so the code below has to
# be a little more verbose
set verbose 1
}
foreach cmd [lsort [uplevel #0 [list info commands $pattern]]] {
if {$force} {
# If -force has been used, then trace any command, no question asked!
lappend commands $cmd
continue
}
# Otherwise, only trace Vivado commands
if {[catch "set tmp \[help $cmd\]" errorstring]} {
continue
}
if {[regexp -nocase -- {Tcl Built-In Commands} $tmp]} {
if {$verbose} { puts " -W- the Tcl command '$cmd' cannot be profiled. Skipped" }
continue
}
# if {[regexp -nocase -- {^(help|source|add|undo|redo|rename_ref|start_gui|stop_gui|show_objects|show_schematic|startgroup|end|endgroup)$} $cmd]} { }
# if {[regexp -nocase -- {^(help|source|read_checkpoint|open_run|add|undo|redo|rename_ref|start_gui|stop_gui|show_objects|show_schematic|startgroup|end|endgroup)$} $cmd]} { }
if {[regexp -nocase -- {^(help|source|add|undo|redo|rename_ref|start_gui|stop_gui|show_objects|show_schematic|startgroup|end|endgroup)$} $cmd]} {
if {$verbose} { puts " -W- the Vivado command '$cmd' cannot be profiled. Skipped" }
continue
}
lappend commands $cmd
}
}
if {[llength $commands] == 0} {
error " -E- no Vivado command matched '$args'"
}
puts " -I- [llength $commands] command(s) added to the profiler"
puts " -I- Command(s): $commands"
set cmdlist [concat $cmdlist $commands]
set cmdlist [lsort -unique $cmdlist]
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:remove
#------------------------------------------------------------------------
# Usage: profiler remove <list>
#------------------------------------------------------------------------
# Remove Vivado command(s) from the profiler
#------------------------------------------------------------------------
proc ::tb::profiler::method:remove {args} {
# Summary :
# Argument Usage:
# Return Value:
# Remove Vivado command(s) from the profiler
variable cmdlist
variable params
if {$params(mode) == {started}} {
error " -E- cannot remove command(s) when the profiler is running. Use 'profiler stop' to stop the profiler"
}
if {[llength $args] == 0} {
error " -E- no argument provided"
}
set commands [list]
foreach pattern [::tb::profiler::lflatten $args] {
foreach cmd [lsort [uplevel #0 [list info commands $pattern]]] {
lappend commands $cmd
}
}
set count 0
set removed [list]
foreach cmd $commands {
if {[lsearch $cmdlist $cmd] != -1} {
incr count
}
::tb::profiler::lremove cmdlist $cmd
lappend removed $cmd
}
set cmdlist [lsort -unique $cmdlist]
puts " -I- $count command(s) have been removed"
puts " -I- Removed command(s): [lsort -unique $removed]"
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:reset
#------------------------------------------------------------------------
# Usage: profiler reset
#------------------------------------------------------------------------
# Reset the profiler
#------------------------------------------------------------------------
proc ::tb::profiler::method:reset {args} {
# Summary :
# Argument Usage:
# Return Value:
# Reset the profiler
variable cmdlist
variable tmstart
variable tmend
variable params
variable db
if {$params(mode) == {started}} {
error " -E- cannot reset the profiler when running. Use 'profiler stop' to stop the profiler"
}
# set cmdlist [list]
set tmstart [list]
set tmend [list]
set db [list]
# set params(collectionResultDisplayLimit) -1
array set params [list formatDelay {default} collectionResultDisplayLimit -1 ]
# array set params [list mode {stopped} formatDelay {default} expandObjects 0 collectionResultDisplayLimit -1 ]
puts " -I- profiler reset"
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:status
#------------------------------------------------------------------------
# Usage: profiler status
#------------------------------------------------------------------------
# Return the status of the profiler
#------------------------------------------------------------------------
proc ::tb::profiler::method:status {args} {
# Summary :
# Argument Usage:
# Return Value:
# Status of the profiler
variable cmdlist
variable params
if {$params(mode) == {started}} {
puts " -I- the profiler is started"
} else {
puts " -I- the profiler is stopped"
}
puts " -I- [llength $cmdlist] command(s) are traced:"
puts " -I- Command(s): $cmdlist"
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:start
#------------------------------------------------------------------------
# Usage: profiler start [<options>]
#------------------------------------------------------------------------
# Start the profiler:
# - adds the 'trace' commands
# - starts the timer
#------------------------------------------------------------------------
proc ::tb::profiler::method:start {args} {
# Summary :
# Argument Usage:
# Return Value:
# Start the profiler (-help)
variable cmdlist
variable tmstart
variable tmend
variable params
if {$params(mode) == {started}} {
error " -E- the profiler is already running. Use 'profiler stop' to stop the profiler"
}
set error 0
set incremental 0
set src_info 0
set collection_display_limit $params(collectionResultDisplayLimit)
set help 0
while {[llength $args]} {
set name [lshift args]
switch -regexp -- $name {
{^-incr$} -
{^-in(cr?)?$} {
set incremental 1
}
{^-src_info$} -
{^-sr(c(_(i(n(fo?)?)?)?)?)?$} {
set src_info 1
}
{^-limit$} -
{^-li(m(it?)?)?$} -
{^-collection_display_limit$} -
{^-co(l(l(e(c(t(i(o(n(_(d(i(s(p(l(a(y(_(l(i(m(it?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?$} {
set collection_display_limit [lshift args]
}
{^-h(e(lp?)?)?$} {
set help 1
}
default {
if {[string match "-*" $name]} {
puts " -E- option '$name' is not a valid option."
incr error
} else {
puts " -E- option '$name' is not a valid option."
incr error
}
}
}
}
if {$error} {
error " -E- some error(s) happened. Cannot continue"
}
if {$help} {
puts [format {
Usage: profiler start
[-incr]
[-src_info]
[-collection_display_limit|-limit <num>]
[-help|-h]
Description: Start the profiler
Example:
profiler start
profiler start -incr -src_info -collection_display_limit 500
} ]
# HELP -->
return {}
}
if {[llength $cmdlist] == 0} {
error " -E- no command has been added to the profiler. Use 'profiler add' to add Vivado commands"
}
if {!$incremental} {
# Reset the profiler
::tb::profiler::method:reset
}
# Used the -src_info to show detailed information on each XDC constraint?
if {$src_info} {
if {[lsearch $cmdlist get_property] != -1} {
puts " -W- Removing 'get_property' from the list of commands to be traced (uncompatible with -src_info)"
::tb::profiler::lremove cmdlist get_property
}
if {[lsearch $cmdlist current_design] != -1} {
puts " -W- Removing 'current_design' from the list of commands to be traced (uncompatible with -src_info)"
::tb::profiler::lremove cmdlist current_design
}
interp alias {} ::tb::profiler::leave {} ::tb::profiler::leave2
} else {
interp alias {} ::tb::profiler::leave {} ::tb::profiler::leave1
}
# Set the parameter tcl.collectionResultDisplayLimit if necessary
catch {
# Catching if the profiler is run outside of Vivado
if {$collection_display_limit != [get_param tcl.collectionResultDisplayLimit]} {
# Save the current parameter value so that it can be restored
# Catch the following code as 'get_param' only works if a project is already opened
catch {
puts " -I- setting the parameter 'tcl.collectionResultDisplayLimit' to '$collection_display_limit'"
set params(collectionResultDisplayLimit:ORG) [get_param tcl.collectionResultDisplayLimit]
set_param tcl.collectionResultDisplayLimit $collection_display_limit
}
}
}
# Add 'trace' on the commands
::tb::profiler::trace_on
# Start the timer
lappend tmstart [clock microseconds]
set params(mode) {started}
if {!$incremental} {
puts " -I- profiler started on [clock format [expr [lindex $tmstart end] / 1000000]]"
} else {
puts " -I- profiler started in incremental mode on [clock format [expr [lindex $tmstart end] / 1000000]]"
}
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:stop
#------------------------------------------------------------------------
# Usage: profiler stop
#------------------------------------------------------------------------
# Stop the profiler
#------------------------------------------------------------------------
proc ::tb::profiler::method:stop {args} {
# Summary :
# Argument Usage:
# Return Value:
# Stop the profiler
variable tmend
variable params
if {$params(mode) == {stopped}} {
error " -E- the profiler is not running. Use 'profiler start' to start the profiler"
}
lappend tmend [clock microseconds]
set params(mode) {stopped}
# Remove 'trace' from the commands
::tb::profiler::trace_off
# Restoring the parameter tcl.collectionResultDisplayLimit
if {[info exists params(collectionResultDisplayLimit:ORG)]} {
# Catch the following code as 'get_param' only works if a project is already opened
catch {
puts " -I- restoring the parameter 'tcl.collectionResultDisplayLimit' to '$params(collectionResultDisplayLimit:ORG)'"
set_param tcl.collectionResultDisplayLimit $params(collectionResultDisplayLimit:ORG)
unset params(collectionResultDisplayLimit:ORG)
}
}
puts " -I- profiler stopped on [clock format [expr [lindex $tmend end] / 1000000]]"
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:summary
#------------------------------------------------------------------------
# Usage: profiler summary [<options>]
#------------------------------------------------------------------------
# Print the profiler summary
#------------------------------------------------------------------------
proc ::tb::profiler::method:summary {args} {
# Summary :
# Argument Usage:
# Return Value:
# Return the profiler summary (-help)
variable cmdlist
variable tmstart
variable tmend
variable params
variable db
variable version
if {$params(mode) == {started}} {
error " -E- the profiler is still running. Use 'profiler stop' to stop the profiler"
}
if {([llength $tmstart] == 0) || ([llength $tmend] == 0)} {
error " -E- the profiler has not been run. Use 'profiler start' to start the profiler"
}
set error 0
set return_string 0
set logfile {}
# Only save summary table inside log file?
set detailedTables 1
set format {table}
# Format to display delays
set formatDelay {default}
# Only the commands that should contribute in the total runtime
set profiledCommands {}
# Commands that do not contribute to the total runtime
# set ignoredCommands [list {open_checkpoint} {read_checkpoint} {read_xdc} {open_run} {open_project}]
set ignoredCommands {}
# -ignored_commands specified? yes:1 / no:0
set userIgnoredCommandsOption 0
# -profiled_commands specified? yes:1 / no:0
set userProfiledCommandsOption 0
# Hide commands that do not contribute to the total runtime
set hideIgnoredCommands 0
set help 0
while {[llength $args]} {
set name [lshift args]
switch -regexp -- $name {
{^-r(e(t(u(r(n(_(s(t(r(i(ng?)?)?)?)?)?)?)?)?)?)?)?$} -
{^-return_string$} {
set return_string 1
}
{^-l(og?)?$} -
{^-log$} {
set logfile [lshift args]
}
{^-c(sv?)?$} -
{^-csv$} {
set format {csv}
}
{^-profiled_commands$} -
{^-p(r(o(f(i(l(e(d(_(c(o(m(m(a(n(ds?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?$} {
set profiledCommands [concat $profiledCommands [lshift args]]
set userProfiledCommandsOption 1
}
{^-ignored_commands$} -
{^-i(g(n(o(r(e(d(_(c(o(m(m(a(n(ds)?)?)?)?)?)?)?)?)?)?)?)?)?)?$} {
set ignoredCommands [concat $ignoredCommands [lshift args]]
set userIgnoredCommandsOption 1
}
{^-hide_ignored$} -
{^-hi(d(e(_(i(g(n(o(r(ed?)?)?)?)?)?)?)?)?)?$} {
set hideIgnoredCommands 1
}
{^-hms$} -
{^-hms?$} {
set formatDelay {hms}
}
{^-f(o(r(m(at?)?)?)?)?$} -
{^-format$} {
set formatDelay [lshift args]
}
{^-summary$} -
{^-su(m(m(a(ry?)?)?)?)?$} {
set detailedTables 0
}
{^-h(e(lp?)?)?$} {
set help 1
}
default {
if {[string match "-*" $name]} {
puts " -E- option '$name' is not a valid option."
incr error
} else {
puts " -E- option '$name' is not a valid option."
incr error
}
}
}
}
switch $formatDelay {
hms -
sec -
ms -
s -
"default" {
}
default {
incr error
puts "-E- invalid format. Valid formats are: sec | ms | hms | default"
}
}
if {$userIgnoredCommandsOption && $userProfiledCommandsOption} {
incr error
puts "-E- options -profiled_commands and -ignored_commands are exclusive"
}
if {$error} {
error " -E- some error(s) happened. Cannot continue"
}
if {$help} {
puts [format {
Usage: profiler summary
[-return_string]
[-log <filename>]
[-summary]
[-hms][-format <hms|sec|ms|default>]
[-csv]
[-help|-h]
Description: Return the profiler summary
Use -hms/-format to change the format of reported delays
Use -summary with -log to only save the summary table inside the log file. By default, the
log file include the detailed tables
Example:
profiler summary
profiler summary -return_string -format hms
profiler summary -log profiler.log -csv
} ]
# HELP -->
return {}
}
set params(formatDelay) $formatDelay
switch ${userIgnoredCommandsOption}${userProfiledCommandsOption} {
00 {
# All commands should be profiled
set profiledCommands {.+}
# Default list of Vivado commands that should not contribute to the total runtime
set ignoredCommands [list {open_checkpoint} {read_checkpoint} {read_xdc} {open_run} {open_project}]
}
01 {
# Default list of Vivado commands that should not contribute to the total runtime
set ignoredCommands {}
}
10 {
# All commands should be profiled
set profiledCommands {.+}
}
11 {
# Not supported - should not reach this condition
}
}
set tbl [::tclapp::xilinx::designutils::prettyTable create]
$tbl header [list {command:} {min} {max} {avg} {total} {ncalls} {%runtime}]
set output [list]
if {$format == {csv}} {
lappend output "# --------- PROFILER STATS ---------------------------------------------"
} else {
lappend output "--------- PROFILER STATS ---------------------------------------------"
}
array set tmp {}
array set cnt {}
array set sum {}
array set min {}
array set max {}
set commands [list]
# Total time inside the traced commands
set totaltime 0
# Total runtime
set totalruntime 0
# Multiple runs if 'profiler start -incr' has been used
foreach t_start $tmstart t_end $tmend {
incr totalruntime [expr $t_end - $t_start]
}
set ID 0
foreach i $db {
lassign $i clk enter cmdline code result src_info
set cmd [lindex $cmdline 0]
# Skip commands that do not belong anymore to the list of commands to be traced
# This can happen if the user remove some commands with 'profiler remove' after
# the profiler was run
if {[lsearch $cmdlist $cmd] == -1} {
continue
}
if {![info exists cnt($cmd)]} {set cnt($cmd) 0}
if {![info exists sum($cmd)]} {set sum($cmd) 0}
if {![info exists min($cmd)]} {set min($cmd) 0}
if {![info exists max($cmd)]} {set max($cmd) 0}
if {$enter} {
lappend tmp($cmd) $clk
} else {
set delta [expr {$clk-[lindex $tmp($cmd) end]}]
if {[llength $tmp($cmd)] == 1} {
unset tmp($cmd)
} else {
set tmp($cmd) [lrange $tmp($cmd) 0 end-1]
}
incr cnt($cmd) 1
incr sum($cmd) $delta
# Some commands should not contribute to the total runtime
# if {![regexp {^(open_checkpoint|read_checkpoint|read_xdc|open_run|open_project)$} $cmd]} {}
if {[regexp [format {^(%s)$} [join $profiledCommands {|}]] $cmd]
&& ![regexp [format {^(%s)$} [join $ignoredCommands {|}]] $cmd]} {
incr totaltime $delta
} else {
if {$hideIgnoredCommands} {
# Hide inside the log file the commands that are ignored
continue
}
}
if {$min($cmd) == 0 || $delta < $min($cmd)} {set min($cmd) $delta}
if {$max($cmd) == 0 || $delta > $max($cmd)} {set max($cmd) $delta}
# Save the command inside the Tcl variable
lappend commands [list $ID $delta $cmdline $code $result $src_info]
incr ID
}
}
if {[llength $tmstart] > 1} {
if {$format == {csv}} {
lappend output "# Number of profiler runs: [llength $tmstart]"
} else {
lappend output "Number of profiler runs: [llength $tmstart]"
}
}
if {$format == {csv}} {
set pct [format {%.2f} [expr {$totaltime*100.0/$totalruntime}]]
lappend output "# Total time: [formatDelay $totalruntime] (${pct}% profiled + [format {%.2f} [expr 100-$pct]]% non-profiled commands)"
} else {
set pct [format {%.2f} [expr {$totaltime*100.0/$totalruntime}]]
lappend output "Total time: [formatDelay $totalruntime] (${pct}% profiled + [format {%.2f} [expr 100-$pct]]% non-profiled commands)"
}
set ncalls 0
foreach cmd $cmdlist {
if {![info exists sum($cmd)]} {
continue
}
set avg [expr {int(1.0*$sum($cmd)/$cnt($cmd))}]
set percent [expr {$sum($cmd)*100.0/($totaltime)}]
# The commands that do not contribute to the total runtime are formatted differently
# if {![regexp {^(open_checkpoint|read_checkpoint|read_xdc|open_run|open_project)$} $cmd]} {}
if {[regexp [format {^(%s)$} [join $profiledCommands {|}]] $cmd]
&& ![regexp [format {^(%s)$} [join $ignoredCommands {|}]] $cmd]} {
$tbl addrow [list $cmd \
[formatDelay $min($cmd)] \
[formatDelay $max($cmd)] \
[formatDelay $avg] \
[formatDelay $sum($cmd)] \
$cnt($cmd) \
[format {%.2f%%} $percent] \
]
incr ncalls $cnt($cmd)
} else {
if {$hideIgnoredCommands} {
# Hide commands that are ignored
continue
}
$tbl addrow [list [format {(%s)} $cmd] \
[format {(%s)} [formatDelay $min($cmd)] ] \
[format {(%s)} [formatDelay $max($cmd)] ] \
[format {(%s)} [formatDelay $avg] ] \
[format {(%s)} [formatDelay $sum($cmd)] ] \
[format {(%s)} $cnt($cmd)] \
{-} \
]
}
}
$tbl separator
$tbl addrow [list {TOTAL} {} {} {} [formatDelay $totaltime] $ncalls {100%}]
# lappend output "--------- PROFILER STATS ---------------------------------------------"
# lappend output "Total time: [expr {$totalruntime/1000.0}]ms ([format %.2f%% [expr {$totaltime*100.0/$totalruntime}]] overhead + non-profiled commands)"
if {$format == {table}} {
lappend output [$tbl print -format lean]
} else {
lappend output [$tbl export -format csv]
# Include a tabular format of the table below the CSV report
foreach i [split [$tbl print -format lean] \n] {
lappend output [format {# %s} $i]
}
}
# Destroy the table
catch {$tbl destroy}
if {$logfile != {}} {
if {[catch {
set FH [open $logfile w]
puts $FH "# ---------------------------------------------------------------------------"
puts $FH [format {# Created on %s with Tcl Profiler (%s)} [clock format [clock seconds]] $version ]
puts $FH "# ---------------------------------------------------------------------------\n"
puts $FH "\n############## STATISTICS #################\n"
# Summary table
foreach i [split [join $output \n] \n] {
# puts $FH [format {# %s} $i]
puts $FH [format {%s} $i]
}
if {$detailedTables} {
puts $FH "\n############## TOP 50 RUNTIMES ############\n"
# Select the top 100 offenders from a runtime perspective
set offenders [lrange [lsort -index 1 -decreasing -integer $commands] 0 49]
set tbl [::tclapp::xilinx::designutils::prettyTable create]
$tbl header [list {ID} {runtime} {result} {command}]
foreach i $offenders {
lassign $i ID delta cmdline code result src_info
if {[string length $cmdline] > 200} {
# Cut the command line at first space after the first 200 characters
set idx [string first " " [string range $cmdline 200 end]]
set cmdline [format {%s ... <%s more characters>} [string range $cmdline 0 [expr 200 + $idx]] [expr [string length $cmdline] -200 -$idx] ]
}
set nbrObjects {n/a}
if {$params(expandObjects)} {
set nbrObjects [format {%s objects} [llength $result]]
} else {
set nbrObjects [regsub "\}" [regsub "\{" $result ""] ""]
}
$tbl addrow [list $ID [formatDelay $delta] $nbrObjects $cmdline]
}
foreach i [split [$tbl print -format lean] \n] {
puts $FH [format {# %s} $i]
}
# Destroy the table
catch {$tbl destroy}
puts $FH "\n############## TOP 10 COLLECTIONS #########\n"
# Report the top 10 largest collections
set tbl [::tclapp::xilinx::designutils::prettyTable create]
$tbl header [list {size} {count} {total} {commands}]
catch {unset collections}
foreach i $commands {
lassign $i ID delta cmdline code result src_info
set count 0
if {![regexp {([0-9]+) object} $result - count]} {
# If $result does not match a string such as '1234 objects' then continue
continue
}
if {$count == 0} {
# Skip empty collections
continue
}
# Save the command that generated the collection => lindex <..> 0
lappend collections($count) [lindex [split $cmdline { }] 0]
}
# Count total number of objects from all collections
set count 0
foreach size [array names collections] {
incr count [expr $size * [llength $collections($size)]]
}
# Keep the 10 largest collections
foreach size [lrange [lsort -integer -decreasing [array names collections]] 0 9] {
$tbl addrow [list $size [llength $collections($size)] [expr $size * [llength $collections($size)]] [lsort -unique $collections($size)] ]
}
foreach i [split [$tbl print -format lean] \n] {
puts $FH [format {# %s} $i]
}
puts $FH [format {# Total number of objects: %s} $count]
# Destroy the table
catch {$tbl destroy}
puts $FH "\n############## DETAILED SUMMARY ###########"
if {$format == {csv}} {
set tbl [::tclapp::xilinx::designutils::prettyTable create]
$tbl header [list {ID} {runtime} {command} {objects}]
}
set row [list]
foreach i $commands {
lassign $i ID delta cmdline code result src_info
set cmd [lindex $cmdline 0]
if {$format == {table}} {
if {$src_info != {}} {
puts $FH "\n# ID:$ID time:[formatDelay $delta] $src_info"
} else {
puts $FH "\n# ID:$ID time:[formatDelay $delta] "
}
} else {
set row [list $ID [format {%.3f} [expr $delta / 1000.0]] ]
}
if {[string length $cmdline] > 1000} {
# Cut the command line at first space after the first 1000 characters
set idx [string first " " [string range $cmdline 1000 end]]
set cmdline [format {%s ... <%s more characters>} [string range $cmdline 0 [expr 1000 + $idx]] [expr [string length $cmdline] -1000 -$idx] ]
}
if {$format == {table}} {
# Table format ...
puts $FH $cmdline
if {$code != 0} {
puts $FH [format { -E- returned error code: %s} $code]
}
if {[regexp {^(report_.+)$} $cmd]} {
# Special treatment if the executed command is a report. In this case
# just print the report as is
if {$result != {}} {
foreach el [split $result \n] {
puts $FH [format {# %s} $el]
}
}
} else {
catch {
if {$result != {}} {
if {[llength $result] == 1} {
puts $FH [format {# %s} $result]
} else {
puts $FH [format {# %d elements:} [llength $result]]
foreach el [lsort $result] {
puts $FH [format {# %s} $el]
}
}
}
}
}
} else {
# CSV format ...
lappend row $cmdline
if {[regexp {^(report_.+)$} $cmd]} {
# Special treatment if the executed command is a report.
lappend row {}
} else {
catch {
if {$result != {}} {
if {[llength $result] == 1} {
lappend row $result
} else {
lappend row [llength $result]
}
}
}
}
}
if {$format == {csv}} {
$tbl addrow $row
}
}
# End "DETAILED SUMMARY"
if {$format == {csv}} {
puts $FH [$tbl export -format csv]
# Destroy the table
catch {$tbl destroy}
}
# End of all detailed tables
}
} errorstring]} {
puts " -I- failed to generate log file '[file normalize $logfile]': $errorstring"
} else {
puts " -I- log file '[file normalize $logfile]' has been created"
}
close $FH
}
if {$return_string} {
return -code ok [join $output \n]
} else {
puts [join $output \n]
return -code ok
}
}
#------------------------------------------------------------------------
# ::tb::profiler::method:time
#------------------------------------------------------------------------
# Usage: profiler time [<options>]
#------------------------------------------------------------------------
# Profile the specified code
#------------------------------------------------------------------------
proc ::tb::profiler::method:time {args} {
# Summary :
# Argument Usage:
# Return Value:
# Profile the inline Tcl code (-help)
variable cmdlist
variable params
if {$params(mode) == {started}} {
error " -E- the profiler is already running. Use 'profiler stop' to stop the profiler"
}
set error 0
set sections [list]
set startOptions [list]
set logfile {}
set help 0
if {[llength $args] == 0} { incr help }
while {[llength $args]} {
set name [lshift args]
switch -regexp -- $name {
{^-incr$} -
{^-in(cr?)?$} {
lappend startOptions {-incr}
}
{^-src_info$} -
{^-sr(c(_(i(n(fo?)?)?)?)?)?$} {
lappend startOptions {-src_info}
}
{^-limit$} -
{^-li(m(it?)?)?$} -
{^-collection_display_limit$} -
{^-co(l(l(e(c(t(i(o(n(_(d(i(s(p(l(a(y(_(l(i(m(it?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?$} {
lappend startOptions {-limit}
lappend startOptions [lshift args]
}
{^-log$} -
{^-log?$} {
set logfile [lshift args]
}
{^-h(e(lp?)?)?$} {
set help 1
}
default {
if {[string match "-*" $name]} {
puts " -E- option '$name' is not a valid option."
incr error
} else {
# Append to the list of Tcl sections(s) to execute
lappend sections $name
}
}
}
}
if {$error} {
error " -E- some error(s) happened. Cannot continue"
}
if {$help} {
puts [format {
Usage: profiler time <SectionOfTclCode>
[-incr]
[-src_info]
[-collection_display_limit|-limit <num>]
[-log <filename>]
[-help|-h]
Description: Run the profiler on an inline Tcl code
Example:
profiler time { read_xdc ./constraints.xdc } -collection_display_limit 500
profiler time -incr -src_info { read_xdc ./constraints.xdc } -log profiler.log
} ]
# HELP -->
return {}
}
if {[llength $cmdlist] == 0} {
error " -E- no command has been added to the profiler. Use 'profiler add' to add Vivado commands"
}
if {[llength $sections] == 0} {
error " -E- no in-line code provided"
}
# Start the profiler
eval [concat ::tb::profiler::method:start $startOptions]
# Execute each section of Tcl code
foreach section $sections {
set res {}
# Needs to be executed 3 levels higher in the stack
if {[catch { set res [uplevel 3 [concat eval $section]] } errorstring]} {
::tb::profiler::method:stop
error " -E- the profiler failed with the following error: $errorstring"
}
}
# Stop the profiler
::tb::profiler::method:stop
# Generate the summary and log file if requested
if {$logfile != {}} {
::tb::profiler::method:summary -log $logfile
}
return -code ok
}
#------------------------------------------------------------------------
# ::tb::profiler::method:configure
#------------------------------------------------------------------------
# Usage: profiler configure [<options>]
#------------------------------------------------------------------------
# Configure some of the profiler parameters
#------------------------------------------------------------------------
proc ::tb::profiler::method:configure {args} {
# Summary :
# Argument Usage:
# Return Value:
# Configure the profiler (-help)
variable params
set error 0
set help 0
if {[llength $args] == 0} {
set help 1
}
while {[llength $args]} {
set name [lshift args]
switch -regexp -- $name {
{^-limit$} -
{^-li(m(it?)?)?$} -
{^-collection_display_limit$} -
{^-co(l(l(e(c(t(i(o(n(_(d(i(s(p(l(a(y(_(l(i(m(it?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?)?$} {
set params(collectionResultDisplayLimit) [lshift args]
}
{^-details$} -
{^-de(t(a(i(ls?)?)?)?)?$} {
set params(expandObjects) 1
}
{^-summary$} -
{^-su(m(m(a(ry?)?)?)?)?$} {
set params(expandObjects) 0
}
{^-h(e(lp?)?)?$} {
set help 1
}
default {
if {[string match "-*" $name]} {
puts " -E- option '$name' is not a valid option."
incr error
} else {
puts " -E- option '$name' is not a valid option."
incr error
}
}
}
}
if {$help} {
puts [format {
Usage: profiler configure
[-collection_display_limit|-limit <num>]
[-summary][-details]
[-help|-h]
Description: Configure the profiler
-details: expand inside the log file the list of objects returned by each command
-summary: summarize inside the log file the number of objects returned by by each command
Default behavior is -summary
Example:
profiler configure -collection_display_limit 500 -details
} ]
# HELP -->
return -code ok
}
return -code ok
}
#################################################################################
namespace import ::tb::profiler
# Information
# profiler -help
# # puts " Add commands to the profiler with:"
# # puts " profiler add *\n"
# profiler add *
report_constraints.tcl
#!/bin/sh
# use -*-TCL-*- \
exec tclsh "$0" -- ${1+"$@"}
####################################################################################################
# HEADER_BEGIN
# COPYRIGHT NOTICE
# Copyright 2001-2018 Xilinx Inc. All Rights Reserved.
# http://www.xilinx.com/support
# HEADER_END
####################################################################################################
########################################################################################
##
## Company: Xilinx, Inc.
## Created by: David Pefourque
##
## Version: 2018.05.31 (Customer Release)
## Description: Constraints summary from XDC or DCP
##
########################################################################################
########################################################################################
## 2018.05.31 - Make the differentiation between user and IP waivers (create_waiver)
## 2018.05.30 - Added support for -timing_graph to estimate the number of timing updates
## - Added support for -details/-full_details
## 2018.04.02 - Added support for -compress_line (-large/-context <line|complete>)
## 2018.03.22 - Added support for gzip-ed input XDC files (-large/-context <line|complete>)
## 2018.02.09 - Improved messaging in verbose mode
## 2018.01.25 - Added support for -context/-large to handle very large XDC (multiple GB)
## 2018.01.11 - Fixed issue -current_design not being recognized
## 2017.12.21 - Fixed issue with XDC not being recognized
## 2017.11.29 - Improved messaging when checkpoint extension does not match .dcp
## 2017.10.04 - Added set_max_time_borrow as timing constraint
## 2017.05.19 - Added support for -no_src_info
## 2017.03.08 - Added 'transpose' method to the Table package
## 2016.04.04 - Modified sh exec command to execute script
## 2016.03.07 - Fixed issue with bus bits reported as constraint
## 2016.01.28 - Ordered XDC files when reading DCP
## 2016.01.26 - Renamed script to report_constraints
## - Renamed namespace to report_constraints
## - Added support for -all/-property
## 2016.01.22 - Added support for -dcp
## 2016.01.20 - Added support for 'set_max_delay -datapath_only' as separate constraint
## - Misc enhancements
## 2016.01.19 - Initial release
########################################################################################
# Example of report:
# +---------------------------------+
# | Timing Constraints Summary |
# +---------------------+-----------+
# | Constraint | step3.xdc |
# +---------------------+-----------+
# | create_clock | 5 |
# | set_clock_groups | 3 |
# | set_input_jitter | 1 |
# | set_false_path | 55 |
# | set_multicycle_path | 10 |
# | set_max_delay | 35 |
# | set_max_delay (DPO) | 4 |
# +---------------------+-----------+
#
# step3.xdc -> /wrk/xsjhdnobkup2/dpefour/bugcases/932532/step3.xdc
# +------------------------+--------------------------------+---------------------------+---------------------------------+
# | Constraint | bb200_top_wrapper_late.xdc (0) | bb200_top_wrapper.xdc (1) | bb200_top_wrapper_early.xdc (2) |
# +------------------------+--------------------------------+---------------------------+---------------------------------+
# | create_clock | 0 | 7 | 9 |
# | create_generated_clock | 0 | 1 | 0 |
# | set_clock_groups | 0 | 2 | 0 |
# | set_input_delay | 0 | 2 | 0 |
# | set_output_delay | 0 | 2 | 0 |
# | set_case_analysis | 0 | 0 | 15 |
# | set_false_path | 23 | 5 | 1435 |
# | set_multicycle_path | 0 | 8 | 2 |
# | set_max_delay | 0 | 3 | 0 |
# | set_max_delay (DPO) | 158 | 9 | 512 |
# | set_min_delay | 0 | 3 | 0 |
# | set_disable_timing | 58 | 0 | 0 |
# +------------------------+--------------------------------+---------------------------+---------------------------------+
# +---------------------------------------------------------------------------------------------------------------------+
# | Vivado Commands Summary |
# +----------------------+--------------------------------+---------------------------+---------------------------------+
# | Command | bb200_top_wrapper_late.xdc (0) | bb200_top_wrapper.xdc (1) | bb200_top_wrapper_early.xdc (2) |
# +----------------------+--------------------------------+---------------------------+---------------------------------+
# | add_cells_to_pblock | 0 | 1 | 2 |
# | all_fanout | 80 | 0 | 0 |
# | create_interface | 0 | 0 | 1 |
# | create_pblock | 0 | 2 | 2 |
# | current_design | 6659 | 2168 | 17138 |
# | current_instance | 174 | 5 | 319 |
# | filter | 85 | 0 | 0 |
# | get_cells | 304 | 15 | 2332 |
# | get_clocks | 0 | 38 | 0 |
# | get_iobanks | 2 | 0 | 0 |
# | get_nets | 0 | 12 | 6 |
# | get_pblocks | 0 | 3 | 4 |
# | get_pins | 17 | 43 | 1149 |
# | get_ports | 85 | 1713 | 74 |
# | make_diff_pair_ports | 0 | 0 | 1 |
# | resize_pblock | 0 | 2 | 2 |
# | set_property | 6663 | 3868 | 17233 |
# +----------------------+--------------------------------+---------------------------+---------------------------------+
namespace eval ::tb {
# namespace export -force report_constraints
}
namespace eval ::tb::utils {
namespace export -force report_constraints
}
namespace eval ::tb::utils::report_constraints {
namespace export -force report_constraints
variable version {2018.05.31 (C)}
variable params
variable output {}
variable metrics
array set params [list format {table} context {full} compress 0 verbose 0 debug 0]
array set metrics [list]
}
proc ::tb::utils::report_constraints::lshift {inputlist} {
upvar $inputlist argv
set arg [lindex $argv 0]
set argv [lrange $argv 1 end]
return $arg
}
proc ::tb::utils::report_constraints::report_constraints {args} {
variable version
variable reports
variable metrics
variable params
variable output
catch {unset metrics}
catch {unset reports}
set params(verbose) 0
set params(debug) 0
set params(format) {table}
set params(context) {full} ; # full|line|complete
set params(compress) 0 ; # Compress the XDC line being read?
set currentDesign 0
set ofilename {}
set ifilenames {}
set checkpoint {}
set filemode {w}
set returnstring 0
set format {default}
set refColumn 0
set returnstring 0
set standalone 0
set reportdetails 0
set reportcommands 0
set reporttiminggraph 0
set showproperty 0
set showsrcinfo 1
set tmpDir {}
set error 0
set help 0
if {([llength $args] == 0) || ($args == {-standalone})} {
set help 1
}
while {[llength $args]} {
set name [lshift args]
switch -regexp -- $name {
{^-cu(r(r(e(n(t(_(d(e(s(i(gn?)?)?)?)?)?)?)?)?)?)?)?$} {
set currentDesign 1
}
{^-f(i(le?)?)?$} {
set ofilename [lshift args]
}
{^-dcp?$} {
set checkpoint [lshift args]
}
{^-csv?$} {
set params(format) {csv}
}
{^-all?$} {
set reportcommands 1
}
{^-pr(o(p(e(r(ty?)?)?)?)?)?$} {
set showproperty 1
}
{^-no(_(s(r(c(_(i(n(fo?)?)?)?)?)?)?)?)?$} {
set showsrcinfo 0
}
{^-xdc?$} {
foreach pattern [lshift args] {
foreach file [glob -nocomplain $pattern] {
if {![file exists $file]} {
puts " -E- File '$file' does not exist"
incr error
continue
}
lappend ifilenames [file normalize $file]
}
}
set ifilenames [lsort -unique $ifilenames]
}
{^-ap(p(e(nd?)?)?)?$} {
set filemode {a}
}
{^-la(r(ge?)?)?$} {
set params(context) {line}
}
{^-co(n(t(e(xt?)?)?)?)?$} {
set params(context) [lshift args]
}
{^-co(m(p(r(e(s(s(_(l(i(ne?)?)?)?)?)?)?)?)?)?)?$} {
set params(compress) 1
}
{^-r(e(t(u(r(n(_(s(t(r(i(ng?)?)?)?)?)?)?)?)?)?)?)?$} {
set returnstring 1
}
{^-timing_graph$} -
{^-ti(m(i(n(g(_(g(r(a(ph?)?)?)?)?)?)?)?)?)?$} {
set reporttiminggraph 1
}
{^-details$} -
{^-de(t(a(i(ls?)?)?)?)?$} {
set reportdetails 1
}
{^-full_details$} -
{^-fu(l(l(_(d(e(t(a(i(ls?)?)?)?)?)?)?)?)?)?$} {
set reportdetails 2
}
{^-standalone$} {
# The script is run in standalone mode
set standalone 1
}
{^-v(e(r(b(o(se?)?)?)?)?)?$} {
set params(verbose) 1
}
{^-d(e(b(ug?)?)?)?$} {
set params(debug) 1
}
{^-h(e(lp?)?)?$} {
set help 1
}
default {
if {[string match "-*" $name]} {
puts " -E- option '$name' is not a valid option."
incr error
} else {
foreach file [glob -nocomplain $name] {
if {![file exists $file]} {
puts " -E- File '$file' does not exist"
incr error
continue
}
lappend ifilenames [file normalize $file]
}
set ifilenames [lsort -unique $ifilenames]
}
}
}
}
if {$help} {
puts [format {
Usage: report_constraints
[<pattern>][-xdc <pattern>]
[-dcp <checkpoint>]
[-all][-property][-no_src_info]
[-file <filename>]
[-append]
[-csv]
[-details][-full_details]
[-timing_graph]
[-verbose|-v]
[-help|-h]
Description: Constraints summary from XDC or DCP
Version: %s
Use -all to report all Vivado commands. By default, only timing constraints are reported.
Use -property with -all to show property values used with set_property command.
Use -no_src_info with -property to hide src_info/SRC_FILE_INFO properties.
Use -timing_graph to estimate the impact of the commands on the timing graph
(-verbose for more details)
Note: this is a coarse estimation that cannot fully account for the timer's behavior
Use -details to report the list of all the commands that impact the timing graph
(-verbose for more details)
Use -full_details to report all the commands (-verbose for more details)
Example:
report_constraints -xdc '*xdc' -all
report_constraints -xdc '*xdc' -all -timing_graph -details -verbose
report_constraints -xdc '*xdc' -csv -file result.csv
report_constraints -dcp top.dcp -csv -file result.csv
} $version ]
# HELP -->
return -code ok
}
switch $params(context) {
full -
line -
complete {
}
default {
puts " -E- invalid value for -context. The valid values are: full|line|complete"
incr error
}
}
if {([llength $ifilenames] == 0) && !$currentDesign && ($checkpoint == {})} {
puts " -E- no valid input file. Use -xdc to specify XDC(s)"
incr error
}
if {[llength $ifilenames] && ($checkpoint != {})} {
puts " -E- cannot use -xdc & -dcp together"
incr error
}
if {($ofilename != {}) && $returnstring} {
puts " -E- cannot use -file & -return_string together"
incr error
}
if {[llength $ifilenames] && $currentDesign} {
puts " -E- cannot use -current_design & -xdc together"
incr error
}
if {($checkpoint != {}) && $currentDesign} {
puts " -E- cannot use -current_design & -dcp together"
incr error
}
if {$standalone && $currentDesign} {
puts " -E- cannot use -current_design in standalone mode"
incr error
}
if {$standalone && $returnstring} {
puts " -E- cannot use -return_string in standalone mode"
incr error
}
if {$showproperty && !$reportcommands} {
puts " -W- -property must be used with -all. Command line option ignored"
# incr error
}
if {($checkpoint == {}) && ([llength $ifilenames] == 1)} {
if {[string tolower [file extension $ifilenames]] == {.dcp}} {
# In this case, the input file is most likely not an XDC but a DCP
set checkpoint $ifilenames
set ifilenames {}
}
}
if {$checkpoint != {}} {
set checkpoint [file normalize $checkpoint]
if {![file exists $checkpoint]} {
puts " -E- checkpoint '$checkpoint' does not exist"
incr error
}
} elseif {[llength $ifilenames]} {
} elseif {$currentDesign} {
} else {
puts " -E- no valid checkpoint/XDC detected. Use -dcp/-xdc to force a file type"
incr error
}
if {$error} {
error " -E- some error(s) happened. Cannot continue"
}
set startTime [clock seconds]
set output [list]
if {$currentDesign} {
# Generate XDC from current design
set xdc [file normalize [format {write_xdc.%s} [clock seconds]]]
if {$reportcommands} {
write_xdc -quiet -file $xdc
} else {
write_xdc -quiet -exclude_physical -file $xdc
}
# write_xdc -quiet -exclude_physical -file $xdc
dputs " -D- writing XDC file '$xdc'"
set ifilenames $xdc
}
if {$checkpoint != {}} {
if {$params(verbose)} {
puts " -I- Processing DCP $checkpoint"
}
set tmpDir [file normalize [format {report_constraints.%s} [clock seconds]] ]
# Extract XDC inside temp directory
uplevel #0 [list exec unzip $checkpoint *.xdc *.xml -d $tmpDir ]
# set ifilenames [lsort [glob -nocomplain $tmpDir/*xdc]]
if {![file exist $tmpDir/dcp.xml]} {
# dcp.xml not found, get all the XDC in any order
puts " -W- cannot find dcp.xml inside DCP"
set ifilenames [lsort [glob -nocomplain $tmpDir/*xdc]]
} else {
# dcp.xml found, get the list of XDC in the order they are inside the XML
set ifilenames [list]
set FH [open $tmpDir/dcp.xml {r}]
set content [split [read $FH] \n]
close $FH
foreach line $content {
# E.g:
# <File Type="XDC_EARLY" Name="example_top_early.xdc" ModTime="1526579139"/>
# <File Type="XDC" Name="example_top.xdc" ModTime="1526579139"/>
# <File Type="XDC_BOARD" Name="example_top_board.xdc" ModTime="1526579139"/>
if {[regexp -nocase {Type=\"XDC} $line]} {
if {[regexp -nocase {Name=\"([^\"]+)\"} $line - name]} {
lappend ifilenames $tmpDir/$name
}
}
}
}
}
if {[catch {
########################################################################################
##
##
##
########################################################################################
# All tracked timing constraints
set timCons [list create_clock \
create_generated_clock \
set_clock_latency \
set_clock_uncertainty \
set_clock_groups \
set_clock_sense \
set_input_jitter \
set_system_jitter \
set_external_delay \
set_input_delay \
set_output_delay \
set_data_check \
set_case_analysis \
set_false_path \
set_multicycle_path \
set_max_delay \
set_max_delay_DPO \
set_min_delay \
group_path \
set_disable_timing \
set_bus_skew \
set_max_time_borrow ]
set allCommands [list]
set index -1
set numColumns [llength $ifilenames]
catch {unset metrics}
set timingGraph {}
foreach file $ifilenames {
dputs " -D- Processing $file"
incr index
catch {unset commands}
catch {unset res}
foreach el $timCons {
set commands($el) 0
}
set res [getVivadoCommands $file $showproperty $showsrcinfo]
array set commands $res
# Save inside $timingGraph the sequential list of enable/disable from all the run commands
if {[info exists commands(-)]} {
foreach el $commands(-) {
foreach {cmd state cmdline} $el { break }
lappend timingGraph [list $file $cmd $state $cmdline]
}
unset commands(-)
}
# Save the list of commands used in this XDC
set allCommands [concat $allCommands [array names commands]]
foreach el [array names commands] {
if {$commands($el) != 0} {
set metrics(${index}:${el}) $commands($el)
}
}
}
set allCommands [lsort -unique $allCommands]
########################################################################################
##
##
##
########################################################################################
set tbl [::Table::Create "Timing Constraints Summary"]
$tbl indent 1
set header [list {Constraint}]
if {$currentDesign} {
lappend header {<CURRENT_DESIGN>}
} else {
set index -1
foreach file $ifilenames {
incr index
lappend header [format {%s (%d)} [file tail $file] $index]
}
}
$tbl header $header
# $tbl configure -indent 2
foreach constraint $timCons {
switch $constraint {
set_max_delay_DPO {
# Format name in table
set row [list {set_max_delay (DPO)}]
}
default {
set row [list $constraint]
}
}
# set row [list $constraint]
for {set index 0} {$index < $numColumns} {incr index} {
if {[info exists metrics(${index}:${constraint})]} {
lappend row $metrics(${index}:${constraint})
} else {
lappend row 0
}
}
if {[lsort -unique [lrange $row 1 end]] == {0}} {
# If none of the XDC have this constraint, then skip it:
# Eg: row = {set_clock_groups 0 0 0 0}
continue
}
$tbl addrow $row
}
# set output [concat $output [split [$tbl export -format $params(format)] \n] ]
switch $params(format) {
table {
set output [concat $output [split [$tbl print] \n] ]
}
csv {
set output [concat $output [split [$tbl csv] \n] ]
if {$ofilename != {}} {
# Append a comment out version of the table
foreach line [split [$tbl print] \n] {
lappend output [format {# %s} $line]
}
}
}
}
catch {$tbl destroy}
########################################################################################
##
##
##
########################################################################################
if {$reportcommands} {
set tbl [::Table::Create {Vivado Commands Summary}]
$tbl indent 1
set header [list {Command}]
if {$currentDesign} {
lappend header {<CURRENT_DESIGN>}
} else {
set index -1
foreach file $ifilenames {
incr index
lappend header [format {%s (%d)} [file tail $file] $index]
}
}
$tbl header $header
# $tbl configure -indent 2
foreach command $allCommands {
if {[lsearch $timCons $command] != -1} {
# Skip timing reconstraints since they have already been reported
continue
}
switch $command {
create_waiver_INT {
# Format name in table
set row [list {create_waiver (IP)}]
}
default {
set row [list $command]
}
}
# set row [list $command]
for {set index 0} {$index < $numColumns} {incr index} {
if {[info exists metrics(${index}:${command})]} {
lappend row $metrics(${index}:${command})
} else {
lappend row 0
}
}
if {[lsort -unique [lrange $row 1 end]] == {0}} {
# If none of the XDC have this constraint, then skip it:
# Eg: row = {set_clock_groups 0 0 0 0}
continue
}
$tbl addrow $row
}
# set output [concat $output [split [$tbl export -format $params(format)] \n] ]
switch $params(format) {
table {
set output [concat $output [split [$tbl print] \n] ]
}
csv {
set output [concat $output [split [$tbl csv] \n] ]
if {$ofilename != {}} {
# Append a comment out version of the table
foreach line [split [$tbl print] \n] {
lappend output [format {# %s} $line]
}
}
}
}
catch {$tbl destroy}
}
########################################################################################
##
##
##
########################################################################################
if {$reportdetails} {
set tbl [::Table::Create "Commands Summary"]
$tbl indent 1
$tbl header [list {Position} {File} {Command} {Status}]
set idx -1
foreach el $timingGraph {
# Increase the constraint position number
incr idx
foreach {file cmd state cmdline} $el { break }
if {$params(verbose)} {
# In verbose mode, replace the command by the command line option
set cmd $cmdline
}
switch $state {
- {
set state {-}
if {$reportdetails == 1} {
# Skip commands that do not impact the timing graph
continue
}
}
enable {
set state {Require valid timing}
}
disable {
set state {Invalidate timing}
}
}
if {$currentDesign} {
$tbl addrow [list $idx {<CURRENT_DESIGN>} $cmd $state]
} else {
$tbl addrow [list $idx [file tail $file] $cmd $state]
}
}
switch $params(format) {
table {
set output [concat $output [split [$tbl print] \n] ]
}
csv {
set output [concat $output [split [$tbl csv] \n] ]
if {$ofilename != {}} {
# Append a comment out version of the table
foreach line [split [$tbl print] \n] {
lappend output [format {# %s} $line]
}
}
}
}
catch {$tbl destroy}
}
########################################################################################
##
##
##
########################################################################################
# +-------------------------------------------------------------------------------------------------------+
# | Timing Graph Summary |
# | Estimated updates: 4 |
# +------------------+-------------------------------------------------------------+----------------------+
# | File | Constraint | Status |
# +------------------+-------------------------------------------------------------+----------------------+
# | top_sp_early.xdc | create_clock -period 8.000 {} | Invalidate timing |
# | top_sp.xdc | get_clocks intclk | Require valid timing |
# | top_sp.xdc | set_disable_timing -from I0 -to O {} | Invalidate timing |
# | top_sp_late.xdc | all_fanout -from {} -flat -endpoints_only -only_cells | Require valid timing |
# | top_sp_late.xdc | create_generated_clock -name clk_main_a0 {} | Invalidate timing |
# | top_sp_late.xdc | get_clocks -of {} | Require valid timing |
# | top_sp_late.xdc | create_generated_clock -name tck -source {} -divide_by 8 {} | Invalidate timing |
# | top_sp_late.xdc | get_clocks clk_main_a0 | Require valid timing |
# +------------------+-------------------------------------------------------------+----------------------+
if {$reporttiminggraph} {
# Estimate the number of timing graph updates by counting the
# number of transitions to state 'enable'
set timGraphUpdates 0
catch {unset primaryClocks}
set prevstate {disable}
set L [list]
set idx -1
foreach el $timingGraph {
# Increase the constraint position number
incr idx
foreach {file cmd state cmdline} $el { break }
if {$state == {-}} {
# Skip constraints that do not impact the timing graph
continue
}
if {$cmd == {create_clock}} {
# Keep track of the primary clocks so that 'get_clocks <name>' can be
# checked against the list primary clocks
# Note: 'get_clocks <primary_clock>' does not require valid timing graph
# Note: this is a very basic check that can be easily defeated but that's
# the best that can be done without the help of the timer
if {[regexp {create_clock.+\-name\s+([^\s]+)\s} $cmdline - name]} {
set primaryClocks($name) $cmdline
}
} elseif {$cmd == {create_generated_clock}} {
if {[regexp {create_generated_clock.+\-name\s+([^\s]+)\s} $cmdline - name]} {
}
} elseif {$cmd == {get_clocks}} {
if {[regexp {get_clocks\s+([^\s]+)} $cmdline - name]} {
if {[info exists primaryClocks($name)]} {
# If the clock name matches a primary clock, then the command does not change
# the state of the timing graph => copy previous state
# Note: this is a very basic check that can be easily defeated but that's
# the best that can be done without the help of the timer
set state $prevstate
}
}
}
if {($state == {enable}) && ($prevstate == {disable})} {
incr timGraphUpdates
}
set prevstate $state
lappend L [list $idx $file $cmd $state $cmdline]
}
# Override the list with the temporary list $L
set timingGraph $L
set tbl [::Table::Create "Timing Graph Summary\nEstimated updates: $timGraphUpdates"]
$tbl indent 1
$tbl header [list {Position} {File} {Command} {Status}]
set prevstate {disable}
set prevstate {-}
foreach el $timingGraph {
foreach {idx file cmd state cmdline} $el { break }
if {$params(verbose)} {
# In verbose mode, replace the command by the command line option
set cmd $cmdline
}
if {($state == {enable}) && ($prevstate != {enable})} {
if {$currentDesign} {
$tbl addrow [list $idx {<CURRENT_DESIGN>} $cmd {Require valid timing}]
} else {
$tbl addrow [list $idx [file tail $file] $cmd {Require valid timing}]
}
} elseif {($state == {disable}) && ($prevstate != {disable})} {
if {$currentDesign} {
$tbl addrow [list $idx {<CURRENT_DESIGN>} $cmd {Invalidate timing}]
} else {
$tbl addrow [list $idx [file tail $file] $cmd {Invalidate timing}]
}
}
set prevstate $state
}
switch $params(format) {
table {
set output [concat $output [split [$tbl print] \n] ]
}
csv {
set output [concat $output [split [$tbl csv] \n] ]
if {$ofilename != {}} {
# Append a comment out version of the table
foreach line [split [$tbl print] \n] {
lappend output [format {# %s} $line]
}
}
}
}
catch {$tbl destroy}
}
########################################################################################
##
##
##
########################################################################################
if {$currentDesign} {
# When -current_design has been used, do not generate the footer
# with the filename
} elseif {($checkpoint != {}) && !$params(debug)} {
# When -dcp has been used but not -debug, do not generate
# the footer with the filename
} else {
set prefix {}
if {($params(format) == {csv}) || ($ofilename != {})} {
# Comment out lines when writting into a file
set prefix {# }
}
lappend output {}
set index -1
foreach file $ifilenames {
incr index
lappend output [format "%s %s (%d) \t -> \t %s" $prefix [file tail $file] $index $file]
}
}
########################################################################################
##
##
##
########################################################################################
} errorstring]} {
puts " -E- $errorstring"
}
if {$currentDesign} {
if {!$params(debug)} {
# Keep the file in debug mode
file delete $ifilenames
}
}
if {$checkpoint != {}} {
if {!$params(debug)} {
# Keep the temp directory in debug mode
if {$tmpDir != {}} { file delete -force $tmpDir }
}
}
if {$params(debug)} {
}
set stopTime [clock seconds]
# puts " -I- report_constraints completed in [expr $stopTime - $startTime] seconds"
if {$ofilename != {}} {
set FH [open $ofilename $filemode]
puts $FH "# ---------------------------------------------------------------------------------"
puts $FH [format {# Created on %s with report_constraints (%s)} [clock format [clock seconds]] $::tb::utils::report_constraints::version ]
puts $FH "# ---------------------------------------------------------------------------------\n"
puts $FH [join $output \n]
close $FH
puts " -I- Generated file [file normalize $ofilename]"
return -code ok
}
if {$returnstring} {
return [join $output \n]
} else {
puts [join $output \n]
}
return -code ok
}
########################################################################################
##
##
##
########################################################################################
proc ::tb::utils::report_constraints::presort_list {l1 l2} {
set l [list]
foreach el $l1 {
if {[lsearch $l2 $el] != -1} {
lappend l $el
}
}
foreach el $l2 {
if {[lsearch $l $el] == -1} {
lappend l $el
}
}
return $l
}
proc ::tb::utils::report_constraints::dputs {args} {
variable params
if {$params(debug)} {
eval [concat puts $args]
}
return -code ok
}
# Generate a list of integers
proc ::tb::utils::report_constraints::iota {from to} {
set out [list]
if {$from <= $to} {
for {set i $from} {$i <= $to} {incr i} {lappend out $i}
} else {
for {set i $from} {$i >= $to} {incr i -1} {lappend out $i}
}
return $out
}
# Return a list of Vivado commands used in a Tcl script.
# Format: <command> <number>
# For example:
# get_nets 35 get_pins 242 set_false_path 162 set_multicycle_path 66 \
# create_generated_clock 67 set_clock_groups 292 current_instance 10 \
# set_case_analysis 15 get_cells 191 get_clocks 717 get_ports 26 create_clock 12
proc ::tb::utils::report_constraints::getVivadoCommands {filename {showproperty 0} {showsrcinfo 0}} {
variable params
set slave [interp create]
# In context != line/complete, the file is read line-by-line. Lines ending with '\' are appended to the next one
if {$params(context) != {full}} {
if {$params(context) == {line}} {
# $params(context) == {line}
set continue {# continue}
} else {
# $params(context) == {complete}
set continue {continue}
}
set code [format {
set filename {%s}
# set FH [open $filename {r}]
if {[regexp {.gz$} $filename]} {
# gzip-ed file
puts " -I- opening gzip-ed file '$filename'"
set FH [open "| zcat $filename" {r}]
} else {
set FH [open $filename {r}]
}
set command {}
set lineno -1
set first 1
set compressLine {%s}
while {![eof $FH]} {
incr lineno
gets $FH line
if {$compressLine && !$first} {
set loop 0
# This is an attempt to compress the XDC line to better support very large lines. The first line
# is not compressed to make sure that the Vivado command name is not being altered.
# The strings are replaced by single character '@'
while {[regsub -all -nocase -- {\s([a-zA-Z0-9\_\-\.\/\[\]\{\}\(\)\?\|\$\"\'\=\~\*\^]{2,})(\s|$)} $line { @ } line]} {
# To be safe ...
incr loop
if {$loop > 1000} { break }
}
}
if {[string index $line end] == "\\"} {
append command [string range $line 0 end-1]
set first 0
continue
}
append command $line
if {![info complete $command]} {
%s
}
if {[regexp {^\s*$} $command]} {
set command {}
continue
}
if {[catch {eval $line} errorstring]} {
puts " ERROR (line $lineno): $errorstring"
if {$debug} {
puts " LINE $lineno: $line"
}
}
set command {}
set append 0
}
close $FH
} $filename $params(compress) $continue ]
} else {
# $params(context) == {full}
set code [format {
source {%s}
} $filename]
}
if {$params(debug)} {
# puts " -D- Script:"
foreach line [split $code \n] {
# puts " $line"
}
}
$slave eval [format {
catch {unset commands}
global commands
global showproperty
global showsrcinfo
global debug
set showproperty %s
set showsrcinfo %s
set debug %s
proc unknown {args} {
global commands
global showproperty
global showsrcinfo
set cmd [lindex $args 0]
if {[regexp {^[0-9]+$} $cmd]} {
return -code ok
}
if {($cmd == {set_max_delay}) && ([lsearch -exact $args {-datapath_only}] != -1)} {
set cmd {set_max_delay_DPO}
} elseif {($cmd == {set_max_delay}) && ([lsearch -regexp $args {-datapath}] != -1)} {
set cmd {set_max_delay_DPO}
}
if {($cmd == {create_waiver}) && ([lsearch -exact $args {-internal}] != -1)} {
set cmd {create_waiver_INT}
}
if {$cmd == {set_property}} {
set value [lindex $args 1]
if {$value == {-quiet}} { set value [lindex $args 2] }
# if {($value == {-dict}) || ($value == {-dictionary})} { set value [lindex $args 3] }
# Skip properties SRC_FILE_INFO and src_info
if {($showsrcinfo == 0) && (($value == {SRC_FILE_INFO}) || ($value == {src_info}))} {
return -code ok
}
# If the command is set_property, then save the property value as well.
# For example:
# set_property (CLOCK_DEDICATED_ROUTE)
# set_property (USER_CLOCK_ROOT)
if {$showproperty} {
# Show property value
set cmd "${cmd} (${value})"
} else {
# Hide the property value
}
}
if {![info exists commands($cmd)]} {
set commands($cmd) 0
}
incr commands($cmd)
if {[string length $args] > 80} {
# Shorten the command line
set cmdline [format {%%s ... <%%s more characters>} [string range $args 0 79] [expr [string length $args] - 80] ]
} else {
set cmdline $args
}
# Keep track of which commands impact the timing graph
switch $cmd {
create_clock -
create_generated_clock -
set_case_analysis -
set_clock_sense -
set_clock_uncertainty -
set_disable_timing -
set_external_delay -
set_propagated_clock {
# Those commands invalidate the timing graph
lappend commands(-) [list $cmd {disable} $cmdline]
}
all_registers -
all_fanout -
all_fanin -
get_clocks -
get_generated_clocks -
all_clocks {
# Those commands require the timing graph to be valid
lappend commands(-) [list $cmd {enable} $cmdline]
}
default {
if {[lsearch -regexp $args {-clock}] >= 0} {
# Those commands require the timing graph to be valid
lappend commands(-) [list $cmd {enable} $cmdline]
} else {
# Those commands do not impact the timing graph
# => state is set to '-'
lappend commands(-) [list $cmd {-} $cmdline]
}
}
}
return -code ok
}
%s
} $showproperty $showsrcinfo $params(debug) $code ]
set result [$slave eval array get commands]
interp delete $slave
return $result
}
namespace eval ::tb::utils {
namespace import -force ::tb::utils::report_constraints::report_constraints
}
namespace eval ::tb {
namespace import -force ::tb::utils::report_constraints
}
###########################################################################
##
## Simple package to Handle printing of tables
##
## %> set tbl [Table::Create {this is my title}]
## %> $tbl header [list "name" "#Pins" "case_value" "user_case_value"]
## %> $tbl addrow [list A/B/C/D/E/F 12 - -]
## %> $tbl addrow [list A/B/C/D/E/F 24 1 -]
## %> $tbl separator
## %> $tbl addrow [list A/B/C/D/E/F 48 0 1]
## %> $tbl indent 0
## %> $tbl print
## +-------------+-------+------------+-----------------+
## | name | #Pins | case_value | user_case_value |
## +-------------+-------+------------+-----------------+
## | A/B/C/D/E/F | 12 | - | - |
## | A/B/C/D/E/F | 24 | 1 | - |
## +-------------+-------+------------+-----------------+
## | A/B/C/D/E/F | 48 | 0 | 1 |
## +-------------+-------+------------+-----------------+
## %> $tbl indent 2
## %> $tbl print
## +-------------+-------+------------+-----------------+
## | name | #Pins | case_value | user_case_value |
## +-------------+-------+------------+-----------------+
## | A/B/C/D/E/F | 12 | - | - |
## | A/B/C/D/E/F | 24 | 1 | - |
## +-------------+-------+------------+-----------------+
## | A/B/C/D/E/F | 48 | 0 | 1 |
## +-------------+-------+------------+-----------------+
## %> $tbl sort {-index 1 -increasing} {-index 2 -dictionary}
## %> $tbl print
## %> $tbl destroy
##
###########################################################################
# namespace eval Table { set n 0 }
# Trick to silence the linter
eval [list namespace eval ::Table {
set n 0
} ]
proc ::Table::Create { {title {}} } { #-- constructor
# Summary :
# Argument Usage:
# Return Value:
variable n
set instance [namespace current]::[incr n]
namespace eval $instance { variable tbl [list]; variable header [list]; variable indent 0; variable title {}; variable numrows 0 }
interp alias {} $instance {} ::Table::do $instance
# Set the title
$instance title $title
set instance
}
proc ::Table::do {self method args} { #-- Dispatcher with methods
# Summary :
# Argument Usage:
# Return Value:
upvar #0 ${self}::tbl tbl
upvar #0 ${self}::header header
upvar #0 ${self}::numrows numrows
switch -- $method {
header {
set header [lindex $args 0]
return 0
}
addrow {
eval lappend tbl $args
incr numrows
return 0
}
separator {
eval lappend tbl {%%SEPARATOR%%}
return 0
}
title {
set ${self}::title [lindex $args 0]
return 0
}
indent {
set ${self}::indent $args
return 0
}
print {
eval ::Table::print $self
}
csv {
eval ::Table::printcsv $self
}
length {
return $numrows
}
sort {
# Each argument is a list of: <lsort arguments>
set command {}
while {[llength $args]} {
if {$command == {}} {
set command "lsort [[namespace parent]::lshift args] \$tbl"
} else {
set command "lsort [[namespace parent]::lshift args] \[$command\]"
}
}
if {[catch { set tbl [eval $command] } errorstring]} {
puts " -E- $errorstring"
} else {
}
}
transpose {
eval ::Table::transpose $self
}
reset {
set ${self}::tbl [list]
set ${self}::header [list]
set ${self}::indent 0
set ${self}::title {}
return 0
}
destroy {
set ${self}::tbl [list]
set ${self}::header [list]
set ${self}::indent 0
set ${self}::title {}
namespace delete $self
return 0
}
default {error "unknown method $method"}
}
}
proc ::Table::print {self} {
upvar #0 ${self}::tbl table
upvar #0 ${self}::header header
upvar #0 ${self}::indent indent
upvar #0 ${self}::title title
set maxs {}
foreach item $header {
lappend maxs [string length $item]
}
set numCols [llength $header]
foreach row $table {
if {$row eq {%%SEPARATOR%%}} { continue }
for {set j 0} {$j<$numCols} {incr j} {
set item [lindex $row $j]
set max [lindex $maxs $j]
if {[string length $item]>$max} {
lset maxs $j [string length $item]
}
}
}
set head " [string repeat " " [expr $indent * 4]]+"
foreach max $maxs {append head -[string repeat - $max]-+}
# Generate the title
if {$title ne {}} {
# The upper separator should something like +----...----+
append res " [string repeat " " [expr $indent * 4]]+[string repeat - [expr [string length [string trim $head]] -2]]+\n"
# Suports multi-lines title
foreach line [split $title \n] {
append res " [string repeat " " [expr $indent * 4]]| "
append res [format "%-[expr [string length [string trim $head]] -4]s" $line]
append res " |\n"
}
}
# Generate the table header
append res $head\n
# Generate the table rows
set first 1
set numsep 0
foreach row [concat [list $header] $table] {
if {$row eq {%%SEPARATOR%%}} {
incr numsep
if {$numsep == 1} { append res $head\n }
continue
} else {
set numsep 0
}
append res " [string repeat " " [expr $indent * 4]]|"
foreach item $row max $maxs {append res [format " %-${max}s |" $item]}
append res \n
if {$first} {
append res $head\n
set first 0
incr numsep
}
}
append res $head
set res
}
proc ::Table::printcsv {self args} {
upvar #0 ${self}::tbl table
upvar #0 ${self}::header header
upvar #0 ${self}::title title
array set defaults [list \
-delimiter {,} \
]
array set options [array get defaults]
array set options $args
set sepChar $options(-delimiter)
set res {}
# Support for multi-lines title
set first 1
foreach line [split $title \n] {
if {$first} {
set first 0
append res "# title${sepChar}[::Table::list2csv [list $line] $sepChar]\n"
} else {
append res "# ${sepChar}[::Table::list2csv [list $line] $sepChar]\n"
}
}
append res "[::Table::list2csv $header $sepChar]\n"
set count 0
set numsep 0
foreach row $table {
incr count
if {$row eq {%%SEPARATOR%%}} {
incr numsep
if {$numsep == 1} {
append res "# [::Table::list2csv {++++++++++++++++++++++++++++++++++++++++++++++++++} $sepChar]\n"
} else {
set numsep 0
}
continue
}
append res "[::Table::list2csv $row $sepChar]\n"
}
return $res
}
proc ::Table::transpose {self} {
upvar #0 ${self}::tbl rows
upvar #0 ${self}::header header
upvar #0 ${self}::title title
upvar #0 ${self}::numrows numrows
if {[lsort -unique $header] == [list {}]} {
# Empty header. The matrix is only made of the table rows
set matrix $rows
} else {
# If header defined, include it in the matrix along with the
# table rows
set matrix [concat [list $header] $rows]
}
# Create template of an empty row for the transposed matrix
# (number of rows of current table)
set row {}
set transpose {}
foreach r $matrix {
# Skip separator rows
if {$r == {%%SEPARATOR%%}} {
continue
}
lappend row {}
}
# Create empty transposed matrix
foreach c [lindex $matrix 0] {
lappend transpose $row
}
# Transpose the matrix: rows become columns
set nr 0
foreach r $matrix {
# Skip separator rows
if {$r == {%%SEPARATOR%%}} {
continue
}
set nc 0
foreach c $r {
lset transpose [list $nc $nr] $c
incr nc
}
incr nr
}
# # Re-create a header with format: header row0 row1 ... rowN
# set header {header}
# set n -1
# foreach el [lrange $row 1 end] {
# lappend header [format {row%d} [incr n]]
# }
# # Save the transposed matrix
# set rows $transpose
# # Update the number of rows
# set numrows [llength $transpose]
# The header is the first row of the transposed matrix
set header [lindex $transpose 0]
# Save the transposed matrix
set rows [lrange $transpose 1 end]
# Update the number of rows
set numrows [llength $rows]
return 0
}
proc ::Table::list2csv { list {sepChar ,} } {
set out ""
set sep {}
foreach val $list {
if {[string match "*\[\"$sepChar\]*" $val]} {
append out $sep\"[string map [list \" \"\"] $val]\"
} else {
append out $sep\"$val\"
}
set sep $sepChar
}
return $out
}
########################################################################################
##
##
##
########################################################################################
if {[file tail [info script]]!=[file tail $argv0]} {
# This file is sourced
} else {
# Remove first '--' due to magic exec: exec tclsh "$0" -- ${1+"$@"}
::tb::utils::report_constraints::lshift ::argv
if {[catch {eval [concat ::tb::utils::report_constraints::report_constraints -standalone $argv]} errorstring]} {
puts $errorstring
exit 1
}
exit 0
}
vivadoRuntime.tcl