缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug

缩短Vivado编译时间(4):时间都去哪儿了

针对特定的设计,就编译时间而言,我们要分析时间都消耗在哪些环节从而有针对性的缩短编译时间。通常,时间可能花费在加载约束上,也可能花费在某个子步骤如布局。有时面对的情况是同一设计在不同Vivado版本上运行时间出现较大差异。

这里我们提供了三个对分析编译时间非常有效的Tcl脚本, 见文末。
report_constraints
我们先来介绍第一个命令:report_constraints。运行该命令之前需要先在Vivado Tcl Console中执行如下命令:
source report_constraints.tcl
同时最好将该脚本与待分析的dcp放置在同一目录下,这样在读取dcp时就只用提供当前工作目录即可。另外,需要注意的是report_constraints只能在Linux系统下运行。report_constraints可报告出设计中所用到的约束的种类(包括时序约束也包括非时序约束)、数量以及有效性。通常情况下,Xilinx建议采用如下顺序描述时序约束。除第10set_bus_skew之外,约束的优先级也是依此顺序的,即1号优先级最低,9号优先级最高。因此,不合理的顺序以及不合理的描述方式会导致一些约束被覆盖,从而造成设计“假收敛”的现象。
图片[1]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
对于一个复杂的设计,除了用户约束文件外,还可能会有IP的约束文件,这些约束文件可能在用户约束文件之前被调用(约束文件属性PROCESSING_ORDEREARLY),也可能在用户约束文件之后被调用(约束文件属性PROCESSING_ORDERLATE)。report_constraints可报告出所有约束文件所包含的约束。
案例1:只报告上述10种时序约束的个数
如下图所示(使用report_constraints时需要在添加作用域即tb::)。该报告第1列显示约束类型,第2列显示需要在用户约束之前调用的约束个数,第3列显示用户约束的个数,第4列显示需要在用户之后调用的约束个数,第5列显示和开发板相关的约束个数(当Part选择为开发板时),最后一列显示OOC综合时用到的约束个数。
图片[2]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
案例2:报告所有约束及约束用到的命令
通过添加-all选项,report_constratins可报告所有约束(时序约束+非时序约束)的数目以及这些约束用到的Tcl命令的个数,如下图所示。在Vivado Commands Summary中,可以看到get_cellsget_pinsget_ports以及set_property等命令的使用情况。
图片[3]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
案例3:报告约束的具体信息
添加选项-details-verbosereport_constratins可报告约束的详细信息,如下图所示。
图片[4]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
案例4:报告时序画像
选项-timing_graph-verbose可用于报告时序画像,如下图所示。当Status列由Invalidate timing变为Required valid timing时,Estimated updates1,这个值越小越好,根据此值可判断约束描述的顺序是否是最优的。
图片[5]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug

 

profiler

Vivado编译时间进行调试时,命令profiler可帮助用户查看特定操作的运行时间,分析指定命令的调用次数,从而确定整体运行时间的瓶颈。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

生成的csv文件可用excel打开,第一部分内容如下。第一列显示了所执行的命令,倒数第2列(ncalls)显示了该命令被调用的次数,最后一列显示了执行该命令所耗时间占整体运行时间的百分比。
图片[6]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
profiler生成报告还能显示前50条最耗时的命令,如下图所示。可用看到除open_checkpoint之外,get_clocks最耗时。
图片[7]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
报告的第三部分可以看到设计中Tcl命令的使用情况,如下图所示。图中commands列(最后一列)为Tcl命令,size列为该命令返回的对象的个数,count列为返回同样个数的命令的个数,total列等于sizecount的乘积。以第122get_cells为例,返回120个对象的get_cells24个,所以总对象个数为120×242880
图片[8]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
profiler命令还可用于查看指定命令的运行时间,这对于发现约束中哪些命令最为耗时很有帮助,使用方法如下所示。从生成报告中可以看到get_cells命令共耗时32.984ms
profiler start
set cells [get_cells
-hier -filter “REF_NAME==FDRE”]
profiler stop
profiler summary
图片[9]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
vivadoRuntime
命令vivadoRuntime可用于查看不同策略下每个阶段所消耗的时间,使用方法如下图所示。每个Elapsed列对应1个策略(也就是1Design Run)。
图片[10]-缩短Vivado编译时间(4):时间都去哪儿了-Xilinx-AMD社区-FPGA CPLD-ChipDebug
使用上述3个命令我们可用快速侦测出约束中耗时的命令以及implementation阶段耗时的子步骤。
profiler.tcl
####################################################################################################
# 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

 

 

 

请登录后发表评论