本文系统解析一套面向 Xilinx FPGA 的专业化时序分析 Tcl 脚本体系,覆盖从 BRAM/URAM 寄存器使用合规性、DSP 嵌入式寄存器(MREG/PREG)配置验证、低深度 SRL 与组合 LUT 检查,到跨 SLR 时序路径分析、CDC 报告、高扇出网络识别等完整链路。所有模块均基于 Vivado 原生 Tcl API 构建,支持 JSON/YAML 配置驱动、多阶段 DCP(综合/布局/布线)适配,并内置 Yosys 流程回退机制,适用于大规模异构 FPGA 设计的工程化时序质量管控。
该套脚本并非通用教程,而是真实工业级项目中沉淀的可复用分析框架,其设计逻辑紧密贴合 计算机基础 中的数字电路时序建模原理,同时在资源调度与路径分析层面体现了对 算法/数据结构(如过滤、排序、树状遍历)的深度运用。
tcl/modules/bram_uram_analysis.tcl
该模块聚焦于 Block RAM(BRAM)与 Ultra RAM(URAM)单元中嵌入式输出寄存器(DOA_REG/DOB_REG)的启用状态检查,识别未按最佳实践使用寄存器的潜在时序瓶颈点。
#########################################################################################
## BRAM and URAM analysis functions
#########################################################################################
namespace eval timing_analysis {
# 获取没有使用嵌入式输出寄存器的BRAM并生成报告
proc get_no_reg_bram {sn used_bram {en 1}} {
if {$en==1} {
puts "Start section $sn: get_no_reg_bram get bram without using embedded registers"
set used_bram_a [filter $used_bram "CASCADE_ORDER_A==NONE && CASCADE_ORDER_B==NONE"]
set used_bram_a_violated {}
if {[llength $used_bram_a]>0} {
foreach i_used_bram_a $used_bram_a {
set douta [get_pins -of $i_used_bram_a -filter "REF_PIN_NAME=~DOADO"]
if {"1" in [get_property IS_CONNECTED $douta]} {
if {[get_property DOA_REG $i_used_bram_a]==0} {
lappend used_bram_a_violated $i_used_bram_a
}
}
set doutb [get_pins -of $i_used_bram_a -filter "REF_PIN_NAME=~DOBDO"]
if {"1" in [get_property IS_CONNECTED $doutb]} {
if {[get_property DOB_REG $i_used_bram_a]==0} {
lappend used_bram_a_violated $i_used_bram_a
}
}
}
set used_bram_a_violated [lsort -unique $used_bram_a_violated]
}
set used_fifo_violated [filter $used_bram "CASCADE_ORDER==NONE && REGISTER_MODE==UNREGISTERED"]
set used_cas_bram_a_violated [filter $used_bram "CASCADE_ORDER_A==LAST && CASCADE_ORDER_B==NONE && DOA_REG==0"]
set used_cas_bram_b_violated [filter $used_bram "CASCADE_ORDER_B==LAST && CASCADE_ORDER_A==NONE && DOB_REG==0"]
set used_cas_bram_ab_violated [filter $used_bram "CASCADE_ORDER_B==LAST && CASCADE_ORDER_A==LAST && (DOB_REG==0 || DOA_REG==0)"]
set used_cas_fifo_violated [filter $used_bram "CASCADE_ORDER==LAST && REGISTER_MODE==UNREGISTERED"]
set used_bram_violated {}
set used_bram_violated [concat $used_bram_a_violated $used_fifo_violated $used_cas_bram_a_violated \
$used_cas_bram_b_violated $used_cas_bram_ab_violated $used_cas_fifo_violated]
set used_bram_violated [lsort -unique $used_bram_violated]
if {[llength $used_bram_violated]>0} {
show_objects $used_bram_violated -name ${sn}_bram_violated
set fn ${sn}_bram_violated
report_critical_cells $fn $used_bram_violated
}
puts "Complete section $sn: get_no_reg_bram"
}
}
# 获取没有使用嵌入式输出寄存器的URAM并生成报告
proc get_no_reg_uram {sn used_uram {en 1}} {
if {$en==1} {
puts "Start section $sn: get_no_reg_uram get uram without using embedded registers"
set used_uram_a [filter $used_uram "CASCADE_ORDER_A==NONE && CASCADE_ORDER_B==NONE"]
set used_uram_a_violated {}
if {[llength $used_uram_a]>0} {
foreach i_used_uram_a $used_uram_a {
set douta [get_pins -of $i_used_uram_a -filter "REF_PIN_NAME=~DOA"]
if {"1" in [get_property IS_CONNECTED $douta]} {
if {[get_property DOA_REG $i_used_uram_a]==0} {
lappend used_uram_a_violated $i_used_uram_a
}
}
set doutb [get_pins -of $i_used_uram_a -filter "REF_PIN_NAME=~DOB"]
if {"1" in [get_property IS_CONNECTED $doutb]} {
if {[get_property DOB_REG $i_used_uram_a]==0} {
lappend used_uram_a_violated $i_used_uram_a
}
}
}
set used_uram_a_violated [lsort -unique $used_uram_a_violated]
}
set used_fifo_violated [filter $used_uram "CASCADE_ORDER==NONE && REGISTER_MODE==UNREGISTERED"]
set used_cas_uram_a_violated [filter $used_uram "CASCADE_ORDER_A==LAST && CASCADE_ORDER_B==NONE && DOA_REG==0"]
set used_cas_uram_b_violated [filter $used_uram "CASCADE_ORDER_B==LAST && CASCADE_ORDER_A==NONE && DOB_REG==0"]
set used_cas_uram_ab_violated [filter $used_uram "CASCADE_ORDER_B==LAST && CASCADE_ORDER_A==LAST && (DOB_REG==0 || DOA_REG==0)"]
set used_cas_fifo_violated [filter $used_uram "CASCADE_ORDER==LAST && REGISTER_MODE==UNREGISTERED"]
set used_uram_violated {}
set used_uram_violated [concat $used_uram_a_violated $used_fifo_violated $used_cas_uram_a_violated \
$used_cas_uram_b_violated $used_cas_uram_ab_violated $used_cas_fifo_violated]
set used_uram_violated [lsort -unique $used_uram_violated]
if {[llength $used_uram_violated]>0} {
show_objects $used_uram_violated -name ${sn}_uram_violated
set fn ${sn}_uram_violated
report_critical_cells $fn $used_uram_violated
}
puts "Complete section $sn: get_no_reg_uram"
}
}
}
tcl/modules/cell_analysis.tcl
该模块针对特定原语单元进行精细化扫描,包括移位寄存器(SRL)、组合 LUT、MUXFX 多路复用器及锁存器(Latch),旨在发现非典型或易被忽略的设计模式。
#########################################################################################
## Cell analysis functions
#########################################################################################
namespace eval timing_analysis {
# 获取深度较小的移位寄存器并生成报告
proc get_lower_depth_srl {sn lower_srl {en 1}} {
if {$en==1} {
puts "Start section $sn: get_lower_depth_srl"
set lower_srl_1 [filter $lower_srl "SRL_MODE < 4"]
set lower_srl_2 [filter $lower_srl "SRL_MODE >= 4 && SRL_MODE < 8"]
set lower_srl_3 [filter $lower_srl "SRL_MODE >= 8 && SRL_MODE < 16"]
if {[llength $lower_srl_1]>0} {
show_objects $lower_srl_1 -name ${sn}_srl1_analysis
report_critical_cells ${sn}_srl1_analysis $lower_srl_1
}
if {[llength $lower_srl_2]>0} {
show_objects $lower_srl_2 -name ${sn}_srl2_analysis
report_critical_cells ${sn}_srl2_analysis $lower_srl_2
}
if {[llength $lower_srl_3]>0} {
show_objects $lower_srl_3 -name ${sn}_srl3_analysis
report_critical_cells ${sn}_srl3_analysis $lower_srl_3
}
puts "Complete section $sn: get_lower_depth_srl"
}
}
# 获取组合LUT并生成报告
proc get_combined_lut {sn combined_lut {en 1}} {
if {$en==1} {
puts "Start section $sn: get_combined_lut"
if {[llength $combined_lut]>0} {
show_objects $combined_lut -name ${sn}_combined_lut_analysis
set fn ${sn}_combined_lut_analysis
report_critical_cells $fn $combined_lut
}
puts "Complete section $sn: get_combined_lut"
}
}
# 获取MUXFX单元并生成报告
proc get_muxfx {sn muxfx_cells {en 1}} {
if {$en==1} {
puts "Start section $sn: get_muxfx"
if {[llength $muxfx_cells]>0} {
show_objects $muxfx_cells -name ${sn}_muxfx_analysis
set fn ${sn}_muxfx_analysis
report_critical_cells $fn $muxfx_cells
}
puts "Complete section $sn: get_muxfx"
}
}
# 获取锁存器单元并生成报告
proc get_latch {sn latch_cells {en 1}} {
if {$en==1} {
puts "Start section $sn: get_latch"
if {[llength $latch_cells]>0} {
show_objects $latch_cells -name ${sn}_latch_analysis
set fn ${sn}_latch_analysis
report_critical_cells $fn $latch_cells
}
puts "Complete section $sn: get_latch"
}
}
}
tcl/modules/common_utils.tcl
提供基础工具函数,包括 FPGA 器件型号标准化(剥离 es 工程样片标识)、序列号生成、以及关键时序基准值查表(timing_table),支撑后续分析模块的参数化运行。
#########################################################################################
## Utility functions for timing analysis
#########################################################################################
namespace eval timing_analysis {
# 移除FPGA器件型号中的工程样片(es)标志
proc remove_es {x_part} {
set x_part_seg [split $x_part "-"]
set x_part_seg_last [lindex $x_part_seg end]
if {[regexp {es.*} $x_part_seg_last]==1} {
set x_part_seg_update [lrange $x_part_seg 0 end-1]
set x_part [join $x_part_seg_update "-"]
}
return $x_part
}
# 根据索引和阶段生成序列号
proc get_sn {index phase} {
if {$index<10} {
set sn "S0$index"
} else {
set sn "S$index"
}
return ${sn}_${phase}
}
# 根据索引和准确性标志获取时序基准值
proc get_timing_base {index is_accurate} {
array set timing_table {
{1,-1} 0.575
{1,-2} 0.500
{1,-3} 0.425
{2,-1} 0.490
{2,-2} 0.425
{2,-3} 0.360
{3,-1} 0.350
{3,-2} 0.300
{3,-3} 0.250
{3,-1LV} 0.490
{3,-2LV} 0.425
{3,-3LV} 0.360
}
if {[array exists timing_table] && [array names timing_table $index] ne ""} {
set delay_i $timing_table($index)
} else {
set delay_i 0.5
}
if {$is_accurate==1} {
set delay $delay_i
} else {
set delay 0.5
}
return $delay
}
}
tcl/modules/config_loader.tcl
实现轻量级 JSON/YAML 配置文件加载器,不依赖外部库,采用正则预处理 + 递归下降解析,支持嵌套对象、数组、布尔值、null 及基本类型转换,是整套脚本可配置化的基石。
package provide config_loader 1.0
namespace eval ::config_loader {
namespace export load_config
}
proc ::config_loader::load_config {file_path} {
if {![file exists $file_path]} {
error "Config file not found: $file_path"
}
set ext [file extension $file_path]
set fp [open $file_path r]
set content [read $fp]
close $fp
if {$ext eq ".json"} {
return [parse_json $content]
} elseif {$ext eq ".yaml" || $ext eq ".yml"} {
return [parse_yaml $content]
} else {
error "Unsupported config format: $ext"
}
}
# -------------------------------------------------------------------------
# Simple JSON Parser
# -------------------------------------------------------------------------
proc ::config_loader::parse_json {json} {
# Basic JSON to Dict converter
# Pre-process: remove newlines to simplify
set json [string map {\n " " \r " " \t " "} $json]
# Tokenize
# Matches: strings (simple), numbers, booleans, null, or structural chars
# Regex captures:
# 1. Quoted string: "[^"]*" (non-escaped quotes only for simplicity)
# 2. Numbers/Keywords: [-0-9.a-z]+
# 3. Structure: [\{\}\[\],:]
# Note: This regex assumes no escaped quotes inside strings.
# For "vivado_bin": "vivado", it works perfectly.
set tokens [regexp -all -inline -- {(?:"[^"]*"|[-0-9.]+|true|false|null|[\{\}\[\],:])} $json]
set index 0
return [parse_json_value tokens index]
}
proc ::config_loader::parse_json_value {tokensName indexName} {
upvar $tokensName tokens
upvar $indexName index
if {$index >= [llength $tokens]} { return "" }
set token [lindex $tokens $index]
incr index
if {$token eq "\{"} {
# Object -> Dict
set res [dict create]
# Check for empty object
if {$index < [llength $tokens] && [lindex $tokens $index] eq "\}"} {
incr index
return $res
}
while {$index < [llength $tokens]} {
set key [parse_json_value tokens index]
# Expect colon
if {$index < [llength $tokens] && [lindex $tokens $index] eq ":"} {
incr index
}
set val [parse_json_value tokens index]
dict set res $key $val
if {$index >= [llength $tokens]} { break }
set next [lindex $tokens $index]
incr index
if {$next eq "\}"} { break }
}
return $res
} elseif {$token eq "\["} {
# Array -> List
set res [list]
if {$index < [llength $tokens] && [lindex $tokens $index] eq "\]"} {
incr index
return $res
}
while {$index < [llength $tokens]} {
lappend res [parse_json_value tokens index]
if {$index >= [llength $tokens]} { break }
set next [lindex $tokens $index]
incr index
if {$next eq "\]"} { break }
}
return $res
} else {
# Atomic value
# Strip quotes if string
if {[string match "\"*\"" $token]} {
return [string range $token 1 end-1]
}
if {$token eq "true"} { return 1 }
if {$token eq "false"} { return 0 }
if {$token eq "null"} { return "" }
return $token
}
}
# -------------------------------------------------------------------------
# Simple YAML Parser
# -------------------------------------------------------------------------
proc ::config_loader::parse_yaml {content} {
# Supports a subset of YAML:
# - Key: Value
# - Nested dictionaries (indentation based)
# - Inline lists [a, b]
# - Comments #
set lines [split $content "\n"]
return [parse_yaml_lines lines 0]
}
proc ::config_loader::parse_yaml_lines {linesVar minIndent} {
upvar $linesVar lines
set result [dict create]
while {[llength $lines] > 0} {
set line [lindex $lines 0]
# Check if empty or comment
if {[regexp {^\s*(#|$)} $line]} {
set lines [lrange $lines 1 end]
continue
}
# Check indent
regexp {^(\s*)} $line match spaces
set indent [string length $spaces]
if {$indent < $minIndent} {
# End of block
return $result
}
# Parse Key: Value
if {[regexp {^\s*([^:]+):\s*(.*)$} $line match key val]} {
set key [string trim $key]
set val [string trim $val]
set lines [lrange $lines 1 end]
# Clean up value
if {$val eq ""} {
# Block start? Check next line
if {[llength $lines] > 0} {
set next_line [lindex $lines 0]
regexp {^(\s*)} $next_line match next_spaces
set next_indent [string length $next_spaces]
if {$next_indent > $indent} {
# Recursively parse block
set val [parse_yaml_lines lines $next_indent]
}
}
} else {
# Handle types
set val [parse_yaml_val $val]
}
dict set result $key $val
} else {
# Skip malformed lines
set lines [lrange $lines 1 end]
}
}
return $result
}
proc ::config_loader::parse_yaml_val {val} {
# Remove quotes
if {[string match "\"*\"" $val] || [string match "'*'" $val]} {
return [string range $val 1 end-1]
}
# Arrays [a, b]
if {[string match "\[*\]" $val]} {
set inner [string range $val 1 end-1]
set listVal [list]
foreach item [split $inner ","] {
lappend listVal [parse_yaml_val [string trim $item]]
}
return $listVal
}
# Booleans
if {$val eq "true"} { return 1 }
if {$val eq "false"} { return 0 }
return $val
}
tcl/modules/csv_reporters.tcl
统一 CSV 报告生成器,输出结构化数据供下游分析或人工审查,涵盖关键单元属性、时序路径详情、BEL 映射关系等核心维度。
#########################################################################################
## Report generation functions
#########################################################################################
namespace eval timing_analysis {
# 生成关键单元的CSV报告
proc report_critical_cells {fn mycells} {
set fid [open ${fn}.csv w]
puts $fid "#\n# File created on [clock format [clock seconds]] \n#\n"
puts $fid "CellName, RefName, ClockName, ClockRate"
set myf "%.2f"
foreach i_mycells $mycells {
set ref_name [get_property REF_NAME [get_cells $i_mycells]]
set clk_pin [get_pins -of [get_cells $i_mycells] -filter "NAME =~ *CLK"]
set clk_name [get_clocks -of $clk_pin -quiet]
if {[llength $clk_name]==0} {
set myclk "Review"
set clk_rate "Review"
} else {
set myclk [lindex $clk_name 0]
set clk_rate [format $myf [expr 1000.0/[get_property PERIOD $myclk]]]
}
puts $fid "$i_mycells, $ref_name, $myclk, $clk_rate"
}
close $fid
puts "CSV file $fn has been created."
}
# 生成单元格信息的CSV报告
proc report_cells {fn mycells} {
set fid [open ${fn}.csv w]
puts $fid "#\n# File created on [clock format [clock seconds]] \n#\n"
puts $fid "CellName, RefName, Parent"
foreach i_mycells $mycells {
set ref_name [get_property REF_NAME [get_cells $i_mycells]]
set parent [get_property PARENT [get_cells $i_mycells]]
if {[llength $parent]==0} {set parent "TOP"}
puts $fid "$i_mycells, $ref_name, $parent"
}
close $fid
puts "CSV file $fn has been created."
}
# 生成详细的时序路径CSV报告
proc create_timing_report {fn paths} {
set fid [open ${fn}.csv w]
puts $fid "#\n# File created on [clock format [clock seconds]] \n#\n"
puts $fid "PathType, StartCell, EndCell, StartPoint, EndPoint, StartClock, EndClock, Requirement, Slack, LogicLevel, Lut, PathDelay, LogicDelay, LogicDelay%, NetDelay, NetDelay%, Skew, Uncertainty, Inter-SLRCompensation"
set myf "%.2f"
foreach paths_i $paths {
set delay_type [get_property DELAY_TYPE $paths_i]
switch -exact -- $delay_type {
"max" {set path_type "setup"}
"min" {set path_type "hold"}
default {set path_type $delay_type}
}
set start_point [get_property STARTPOINT_PIN $paths_i]
set start_cell [get_property REF_NAME [get_cells -of [get_pins $start_point]]]
set end_point [get_property ENDPOINT_PIN $paths_i]
set end_cell [get_property REF_NAME [get_cells -of [get_pins $end_point]]]
set start_clk [get_property STARTPOINT_CLOCK $paths_i]
if {[llength $start_clk] == 0} {set start_clk No}
set end_clk [get_property ENDPOINT_CLOCK $paths_i]
if {[llength $end_clk] == 0} {set end_clk No}
set req [get_property REQUIREMENT $paths_i]
if {[llength $req] == 0} {
set req inf
set slack inf
} else {
set req [format $myf $req]
set slack [get_property SLACK $paths_i]
if {[llength $slack] > 0} {set slack [format $myf $slack]}
}
set logic_level [get_property LOGIC_LEVELS $paths_i]
set num_luts [llength [get_cells -filter {REF_NAME =~ LUT*} -of $paths_i -quiet]]
set path_delay [format $myf [get_property DATAPATH_DELAY $paths_i]]
set logic_delay [format $myf [get_property DATAPATH_LOGIC_DELAY $paths_i]]
set net_delay [format $myf [get_property DATAPATH_NET_DELAY $paths_i]]
if {$path_delay == 0 || $logic_delay == 0} {
set logic_delay_percent 0.0%
} else {
set logic_delay_percent [format "%.0f%%" [expr double($logic_delay)/double($path_delay)*100]]
}
if {$path_delay == 0 || $net_delay == 0} {
set net_delay_percent 0.0%
} else {
set net_delay_percent [format "%.0f%%" [expr double($net_delay)/double($path_delay)*100]]
}
set skew [get_property SKEW $paths_i]
if {[llength $skew] == 0} {
set skew inf
} else {
set skew [format $myf $skew]
}
set uncertainty [get_property UNCERTAINTY $paths_i]
if {[llength $uncertainty] == 0} {
set uncertainty inf
} else {
set uncertainty [format $myf $uncertainty]
}
set compensation [get_property INTER_SLR_COMPENSATION $paths_i]
if {[llength $compensation]==0} {set compensation "Empty"}
puts $fid "$path_type, $start_cell, $end_cell, $start_point, $end_point, $start_clk, $end_clk, $req, $slack, $logic_level, $num_luts, $path_delay, $logic_delay, $logic_delay_percent, $net_delay, $net_delay_percent, $skew, $uncertainty, $compensation"
}
close $fid
puts "CSV file $fn has been created."
}
# 生成单元和BEL对应关系的CSV报告
proc report_cell_bel {fn cell bel} {
set fid [open ${fn}.csv w]
puts $fid "#\n# File created on [clock format [clock seconds]] \n#\n"
puts $fid "CellName, BelName"
foreach i_cell $cell i_bel $bel {
puts $fid "$i_cell, $i_bel"
}
close $fid
puts "Cells and their bels are recorded"
}
}
tcl/modules/design_analysis.tcl
面向顶层设计质量的综合性分析模块,覆盖时钟特性(频率、偏斜、根节点)、拥塞、CDC、QoR、方法学检查等,是评估设计健康度的关键入口。
#########################################################################################
## Design and Clock analysis functions
#########################################################################################
namespace eval timing_analysis {
# 生成时钟特性报告
proc report_clock_characteristics {sn {en 1}} {
if {$en==1} {
set myf "%.2f"
set max_paths 10
set fn ${sn}_clock_characteristics_analysis
set fid [open ${fn}.csv w]
puts $fid "#\n# File created on [clock format [clock seconds]] \n#\n"
puts $fid "Name, Driver, Freq, Period, Skew(Setup), Skew(Hold), ClockRoot, CrossingSLRs, FlatPinCnt, HighPriority, UserClkRoot"
set gclk_net [get_nets -hier -filter "TYPE==GLOBAL_CLOCK" -top_net_of_hierarchical_group]
if {[llength $gclk_net]>0} {
foreach i_gclk_net $gclk_net {
set i_gclk [get_clocks -of $i_gclk_net -quiet]
if {[llength $i_gclk]>0} {
set i_period [get_property PERIOD $i_gclk]
set i_freq [format $myf [expr 1000.0/$i_period]]
set i_clock_root [get_property CLOCK_ROOT $i_gclk_net]
if {[llength $i_clock_root]==0} {set i_clock_root "Unknown"}
set i_crossing_slrs [get_property CROSSING_SLRS $i_gclk_net]
if {[llength $i_crossing_slrs]==0} {set i_crossing_slrs "Unknown"}
set i_flat_pin_cnt [get_property FLAT_PIN_COUNT $i_gclk_net]
if {[llength $i_flat_pin_cnt]==0} {set i_flat_pin_cnt "Unknown"}
set i_cell [get_cells -of [get_pins -of $i_gclk_net -leaf -filter "DIRECTION==OUT"]]
set i_driver [get_property REF_NAME $i_cell]
set i_high_priority [get_property HIGH_PRIORITY $i_gclk_net]
if {[llength $i_high_priority]==0} {set i_high_priority "Unknown"}
set i_user_clock_root [get_property USER_CLOCK_ROOT $i_gclk_net]
if {[llength $i_user_clock_root]==0} {set i_user_clock_root "Unknown"}
set i_paths_setup [get_timing_paths -from $i_gclk -to $i_gclk -setup -max_paths $max_paths -nworst 1]
set setup_skew_abs [list]
if {[llength $i_paths_setup]>0} {
set setup_skew_val [get_property SKEW $i_paths_setup]
foreach i_setup_skew_val $setup_skew_val {
lappend setup_skew_abs [expr abs($i_setup_skew_val)]
}
set max_setup_skew [lindex [lsort -decreasing $setup_skew_abs] 0]
set i_paths_hold [get_timing_paths -from $i_gclk -to $i_gclk -hold -max_paths $max_paths -nworst 1]
set hold_skew_abs [list]
set hold_skew_val [get_property SKEW $i_paths_hold]
foreach i_hold_skew_val $hold_skew_val {
lappend hold_skew_abs [expr abs($i_hold_skew_val)]
}
set max_hold_skew [lindex [lsort -decreasing $hold_skew_abs] 0]
puts $fid "$i_gclk, $i_driver, $i_freq, $i_period, $max_setup_skew, $max_hold_skew, $i_clock_root, $i_crossing_slrs, $i_flat_pin_cnt, $i_high_priority, $i_user_clock_root"
}
}
}
close $fid
}
puts "Complete section $sn: report_clock_characteristics"
}
}
# 生成时钟网络报告
proc my_clock_networks {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: report_clock_networks"
set fn ${sn}_clock_networks_analysis
report_clock_networks -name $fn -file ${fn}.rpt
puts "Complete section $sn: report_clock_networks"
}
}
# 生成时钟交互报告
proc my_clock_interaction {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: report_clock_interaction"
set fn ${sn}_clock_interaction_analysis
report_clock_interaction -name $fn -file ${fn}.rpt
puts "Complete section $sn: report_clock_interaction"
}
}
# 报告设计的拥塞程度
proc report_congestion_level {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: report_congestion_level"
set fn ${sn}_congestion_analysis
report_design_analysis -analysis_type {Congestion Complexity} -name $fn -file ${fn}.rpt
puts "Complete section $sn: report_congestion_level"
}
}
# 生成设计方法学报告
proc my_report_methodology {sn {en 1} } {
if {$en==1} {
puts "Start section $sn: report_methodology"
set fn ${sn}_methodology_analysis
report_methodology -file ${fn}.rpt -name $fn
puts "Complete section $sn: report_methodology"
}
}
# 生成failfast报告
proc report_failfast {sn slrs dcp_type {en 1} } {
set syn_dcp {1 2}
if {$en==1} {
puts "Start section $sn: report_failfast"
set fn ${sn}_failfast_analysis
if {$slrs==1} {
xilinx::designutils::report_failfast -detailed_reports impl -file ${fn}.rpt
} else {
if {$dcp_type in $syn_dcp} {
xilinx::designutils::report_failfast -detailed_reports impl -file ${fn}.rpt
} else {
xilinx::designutils::report_failfast -by_slr -detailed_reports impl -file ${fn}.rpt
}
}
puts "Complete section $sn: report_failfast"
}
}
# 生成资源利用率报告
proc my_report_utilization {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: report_utilization"
set fn ${sn}_util_analysis
report_utilization -name $fn -file ${fn}.rpt
puts "Complete section $sn: report_utilization"
}
}
# 生成CDC(时钟域交叉)报告
proc my_report_cdc {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: report_cdc"
set fn ${sn}_cdc_analysis
report_cdc -no_header -severity {Critical} -name $fn -file ${fn}.rpt
puts "Complete section $sn: report_cdc"
}
}
# 生成例外情况的总结报告
proc my_report_exceptions {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: report_exceptions"
set fn ${sn}_exceptions_analysis
report_exceptions -summary -name ${fn} -file ${fn}.rpt
puts "Complete section $sn: report_exceptions"
}
}
# 生成高扇出网络报告
proc high_fanout_nets {sn slrs dcp_type {en 1}} {
set syn_dcp {1 2}
if {$en==1} {
puts "Start section $sn: report_high_fanout_nets"
set fn ${sn}_high_fanout_nets_analysis
if {$slrs==1} {
report_high_fanout_nets -name ${fn} -file ${fn}.rpt
} else {
if {$dcp_type in $syn_dcp} {
report_high_fanout_nets -name ${fn} -file ${fn}.rpt
} else {
report_high_fanout_nets -slr -name ${fn} -file ${fn}.rpt
}
}
puts "Complete section $sn: report_high_fanout_nets"
}
}
# 生成RAM利用率报告
proc my_ram_util {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: report_ram_utilization"
set fn ${sn}_ram_util
report_ram_utilization -file ${fn}.rpt
}
}
}
tcl/modules/dsp_analysis.tcl
专用于 DSP 单元的寄存器使用合规性检查,重点识别 MREG(乘法器寄存器)与 PREG(累加器寄存器)未启用但存在乘法操作的场景,避免因流水线断裂导致的性能损失。
#########################################################################################
## DSP analysis functions
#########################################################################################
namespace eval timing_analysis {
# 获取没有使用MREG寄存器的DSP并生成报告
proc get_no_mreg_dsp {sn used_dsp {en 1}} {
if {$en==1} {
puts "Start section $sn: get_no_mreg_dsp get dsp without using embedded MREG"
set used_dsp_0 [filter $used_dsp "AREG==0 && BREG==0 && MREG==0"]
set used_dsp_1 [filter $used_dsp "AREG==1 && BREG==1 && MREG==1"]
set used_dsp_violated {}
if {[llength $used_dsp_0]>0} {
foreach i_used_dsp $used_dsp_0 {
set d [get_pins -of $i_used_dsp -filter "REF_PIN_NAME=~D"]
if {"1" in [get_property IS_CONNECTED $d]} {
if {[get_property USE_MULT $i_used_dsp]!="NONE"} {
lappend used_dsp_violated $i_used_dsp
}
}
}
}
if {[llength $used_dsp_1]>0} {
foreach i_used_dsp $used_dsp_1 {
if {[get_property USE_MULT $i_used_dsp]=="MULTIPLY"} {
if {[get_property MREG $i_used_dsp]==0} {
lappend used_dsp_violated $i_used_dsp
}
}
}
}
set used_dsp_violated [lsort -unique $used_dsp_violated]
if {[llength $used_dsp_violated]>0} {
show_objects $used_dsp_violated -name ${sn}_dsp_mreg_violated
set fn ${sn}_dsp_mreg_violated
report_critical_cells $fn $used_dsp_violated
}
puts "Complete section $sn: get_no_mreg_dsp"
}
}
# 获取没有使用PREG寄存器的DSP并生成报告
proc get_no_preg_dsp {sn used_dsp {en 1}} {
if {$en==1} {
puts "Start section $sn: get_no_preg_dsp get dsp without using embedded PREG"
set used_dsp_violated [filter $used_dsp "PREG==0 && USE_MULT==MULTIPLY"]
if {[llength $used_dsp_violated]>0} {
show_objects $used_dsp_violated -name ${sn}_dsp_preg_violated
set fn ${sn}_dsp_preg_violated
report_critical_cells $fn $used_dsp_violated
}
puts "Complete section $sn: get_no_preg_dsp"
}
}
}
tcl/modules/params_utils.tcl
参数管理中枢,定义了三类运行模式(用户自定义/默认/完整),并校验 vivado_version(正则 20[12][0-9][.][1-4])、dcp_type(1–6)、mode(1–3)等关键输入的合法性,确保脚本在受控前提下执行。
package provide params_utils 1.0
namespace eval ::params_utils {
proc set_defaults {} {
uplevel 1 {
#########################################################################################
# 设置主要参数
#########################################################################################
if {![info exists vivado_version]} { set vivado_version 2022.2 } ;# Vivado版本号
if {![info exists dcp_type]} { set dcp_type 5 } ;# DCP类型
if {![info exists dcp_is_open]} { set dcp_is_open 0 } ;# DCP文件状态
if {![info exists dcp_name]} { set dcp_name *routed.dcp };# DCP文件名模式
if {![info exists mode]} { set mode 1 } ;# 运行模式
if {![info exists is_accurate]} { set is_accurate 1 } ;# 时序计算精度
if {![info exists max_paths]} { set max_paths 100 } ;# 最大路径数
#########################################################################################
# 检查Vivado版本是否合法
# 使用正则表达式验证版本格式是否为dddd.d,其中d为数字,如2022.2
if {[regexp {20[12][0-9][.][1-4]$} $vivado_version]==0} {
puts "Vivado version is not correct"
return -code 1
}
#########################################################################################
# 用户自定义参数设置(模式1)
# 以下参数控制各时序分析功能是否启用(1=启用, 0=禁用)
if {![info exists user_check_timing]} { set user_check_timing 1 }
if {![info exists user_timing_summary]} { set user_timing_summary 1 }
if {![info exists user_neg_slack_timing]} { set user_neg_slack_timing 1 }
if {![info exists user_block2block]} { set user_block2block 1 }
if {![info exists user_high_logic_level]} { set user_high_logic_level 1 }
if {![info exists user_crossing_slrs]} { set user_crossing_slrs 0 }
if {![info exists user_get_cells_crossing_slrs]} { set user_get_cells_crossing_slrs 0 }
if {![info exists user_check_bram]} { set user_check_bram 1 }
if {![info exists user_check_uram]} { set user_check_uram 1 }
if {![info exists user_check_dsp_mreg]} { set user_check_dsp_mreg 1 }
if {![info exists user_check_dsp_preg]} { set user_check_dsp_preg 1 }
if {![info exists user_check_lower_srl]} { set user_check_lower_srl 1 }
if {![info exists user_check_combined_lut]} { set user_check_combined_lut 1 }
if {![info exists user_check_muxfx]} { set user_check_muxfx 1 }
if {![info exists user_check_latch]} { set user_check_latch 1 }
if {![info exists user_clock_characteristics]} { set user_clock_characteristics 1 }
if {![info exists user_clock_networks]} { set user_clock_networks 1 }
if {![info exists user_cdc]} { set user_cdc 1 }
if {![info exists user_clock_interaction]} { set user_clock_interaction 1 }
if {![info exists user_congestion]} { set user_congestion 1 }
if {![info exists user_exceptions]} { set user_exceptions 1 }
if {![info exists user_util]} { set user_util 1 }
if {![info exists user_methodology]} { set user_methodology 1 }
if {![info exists user_qor]} { set user_qor 0 }
if {![info exists user_failfast]} { set user_failfast 0 }
if {![info exists user_high_fanout_nets]} { set user_high_fanout_nets 1 }
if {![info exists user_ram_util]} { set user_ram_util 1 }
# 默认参数设置(模式2)
# 以下参数为默认模式下各时序分析功能的启用状态(1=启用, 0=禁用)
if {![info exists default_check_timing]} { set default_check_timing 1 }
if {![info exists default_timing_summary]} { set default_timing_summary 1 }
if {![info exists default_neg_slack_timing]} { set default_neg_slack_timing 1 }
if {![info exists default_block2block]} { set default_block2block 1 }
if {![info exists default_high_logic_level]} { set default_high_logic_level 0 }
if {![info exists default_crossing_slrs]} { set default_crossing_slrs 1 }
if {![info exists default_get_cells_crossing_slrs]}{ set default_get_cells_crossing_slrs 0 }
if {![info exists default_check_bram]} { set default_check_bram 1 }
if {![info exists default_check_uram]} { set default_check_uram 1 }
if {![info exists default_check_dsp_mreg]} { set default_check_dsp_mreg 1 }
if {![info exists default_check_dsp_preg]} { set default_check_dsp_preg 1 }
if {![info exists default_check_lower_srl]} { set default_check_lower_srl 0 }
if {![info exists default_check_combined_lut]} { set default_check_combined_lut 0 }
if {![info exists default_check_muxfx]} { set default_check_muxfx 0 }
if {![info exists default_check_latch]} { set default_check_latch 0 }
if {![info exists default_clock_characteristics]} { set default_clock_characteristics 0 }
if {![info exists default_clock_networks]} { set default_clock_networks 1 }
if {![info exists default_cdc]} { set default_cdc 0 }
if {![info exists default_clock_interaction]} { set default_clock_interaction 1 }
if {![info exists default_congestion]} { set default_congestion 0 }
if {![info exists default_exceptions]} { set default_exceptions 0 }
if {![info exists default_util]} { set default_util 0 }
if {![info exists default_methodology]} { set default_methodology 1 }
if {![info exists default_qor]} { set default_qor 0 }
if {![info exists default_failfast]} { set default_failfast 0 }
if {![info exists default_high_fanout_nets]} { set default_high_fanout_nets 0 }
if {![info exists default_ram_util]} { set default_ram_util 1 }
}
}
proc check_dcp_and_mode {} {
uplevel 1 {
#########################################################################################
# 检查dcp_type和mode参数是否在合理范围内
#########################################################################################
# 检查dcp_type是否在1-6范围内
if {[regexp {^[1-6]$} $dcp_type] == 0} {
puts "dcp_type的取值范围应在1到6之间."
return -code 1
} else {
puts "dcp_type变量取值合理,继续执行."
}
# 检查mode是否在1-3范围内
if {[regexp {^[1-3]$} $mode] == 0} {
puts "mode的取值范围应在1到3之间."
return -code 1
} else {
puts "mode变量取值合理,继续执行."
}
# 根据dcp_type设置当前阶段名称
switch -exact -- $dcp_type {
1 {set phase "Syn"} ;# 综合阶段
2 {set phase "Opt"} ;# 优化阶段
3 {set phase "Place"} ;# 布局阶段
4 {set phase "PhyPlace"} ;# 物理优化后(布局后)阶段
5 {set phase "Route"} ;# 布线阶段
6 {set phase "PhyRoute"} ;# 物理优化后(布线后)阶段
default {set phase "X"} ;# 未知阶段
}
#########################################################################################
# 根据运行模式确定具体的检查项
#########################################################################################
switch -exact $mode {
1 { ;# 模式1: 用户定义模式
set en_check_timing $user_check_timing ;# 时序检查启用标志
set en_timing_summary $user_timing_summary ;# 时序摘要报告启用标志
set en_neg_slack_timing $user_neg_slack_timing ;# 负时序裕度路径报告启用标志
set en_block2block $user_block2block ;# 块间路径报告启用标志
set en_high_logic_level $user_high_logic_level ;# 高逻辑级数路径报告启用标志
set en_crossing_slrs $user_crossing_slrs ;# 跨SLR路径报告启用标志
set en_get_cells_crossing_slrs $user_get_cells_crossing_slrs ;# 获取跨SLR单元启用标志
set en_check_bram $user_check_bram ;# BRAM检查启用标志
set en_check_uram $user_check_uram ;# URAM检查启用标志
set en_check_dsp_mreg $user_check_dsp_mreg ;# DSP MREG检查启用标志
set en_check_dsp_preg $user_check_dsp_preg ;# DSP PREG检查启用标志
set en_check_lower_srl $user_check_lower_srl ;# 低深度SRL检查启用标志
set en_check_combined_lut $user_check_combined_lut ;# 组合LUT检查启用标志
set en_check_muxfx $user_check_muxfx ;# MUXFX检查启用标志
set en_check_latch $user_check_latch ;# 锁存器检查启用标志
set en_clock_characteristics $user_clock_characteristics ;# 时钟特性报告启用标志
set en_clock_networks $user_clock_networks ;# 时钟网络报告启用标志
set en_cdc $user_cdc ;# 时钟域交叉(CDC)报告启用标志
set en_clock_interaction $user_clock_interaction ;# 时钟交互报告启用标志
set en_congestion $user_congestion ;# 拥塞报告启用标志
set en_exceptions $user_exceptions ;# 时序例外报告启用标志
set en_util $user_util ;# 资源利用率报告启用标志
set en_methodology $user_methodology ;# 设计方法学报告启用标志
set en_qor $user_qor ;# QOR报告启用标志
set en_failfast $user_failfast ;# 快速失败分析启用标志
set en_high_fanout_nets $user_high_fanout_nets ;# 高扇出网络报告启用标志
set en_ram_util $user_ram_util ;# RAM利用率报告启用标志
}
2 { ;# 模式2: 默认模式
set en_check_timing $default_check_timing ;# 时序检查启用标志
set en_timing_summary $default_timing_summary ;# 时序摘要报告启用标志
set en_neg_slack_timing $default_neg_slack_timing ;# 负时序裕度路径报告启用标志
set en_block2block $default_block2block ;# 块间路径报告启用标志
set en_high_logic_level $default_high_logic_level ;# 高逻辑级数路径报告启用标志
set en_crossing_slrs $default_crossing_slrs ;# 跨SLR路径报告启用标志
set en_get_cells_crossing_slrs $default_get_cells_crossing_slrs ;# 获取跨SLR单元启用标志
set en_check_bram $default_check_bram ;# BRAM检查启用标志
set en_check_uram $default_check_uram ;# URAM检查启用标志
set en_check_dsp_mreg $default_check_dsp_mreg ;# DSP MREG检查启用标志
set en_check_dsp_preg $default_check_dsp_preg ;# DSP PREG检查启用标标
set en_check_lower_srl $default_check_lower_srl ;# 低深度SRL检查启用标志
set en_check_combined_lut $default_check_combined_lut ;# 组合LUT检查启用标志
set en_check_muxfx $default_check_muxfx ;# MUXFX检查启用标志
set en_check_latch $default_check_latch ;# 锁存器检查启用标志
set en_clock_characteristics $default_clock_characteristics ;# 时钟特性报告启用标志
set en_clock_networks $default_clock_networks ;# 时钟网络报告启用标志
set en_cdc $default_cdc ;# 时钟域交叉(CDC)报告启用标志
set en_clock_interaction $default_clock_interaction ;# 时钟交互报告启用标志
set en_congestion $default_congestion ;# 拥塞报告启用标志
set en_exceptions $default_exceptions ;# 时序例外报告启用标志
set en_util $default_util ;# 资源利用率报告启用标志
set en_methodology $default_methodology ;# 设计方法学报告启用标志
set en_qor $default_qor ;# QOR报告启用标志
set en_failfast $default_failfast ;# 快速失败分析启用标志
set en_high_fanout_nets $default_high_fanout_nets ;# 高扇出网络报告启用标志
set en_ram_util $default_ram_util ;# RAM利用率报告启用标志
}
3 { ;# 模式3: 完整模式
set en_check_timing 1 ;# 启用时序检查
set en_timing_summary 1 ;# 启用时序摘要报告
set en_neg_slack_timing 1 ;# 启用负时序裕度路径报告
set en_block2block 1 ;# 启用块间路径报告
set en_high_logic_level 1 ;# 启用高逻辑级数路径报告
set en_crossing_slrs 1 ;# 启用跨SLR路径报告
set en_get_cells_crossing_slrs 1 ;# 启用获取跨SLR单元
set en_check_bram 1 ;# 启用BRAM检查
set en_check_uram 1 ;# 启用URAM检查
set en_check_dsp_mreg 1 ;# 启用DSP MREG检查
set en_check_dsp_preg 1 ;# 启用DSP PREG检查
set en_check_lower_srl 1 ;# 启用低深度SRL检查
set en_check_combined_lut 1 ;# 启用组合LUT检查
set en_check_muxfx 1 ;# 启用MUXFX检查
set en_check_latch 1 ;# 启用锁存器检查
set en_clock_characteristics 1 ;# 启用时钟特性报告
set en_clock_networks 1 ;# 启用时钟网络报告
set en_cdc 1 ;# 启用时钟域交叉(CDC)报告
set en_clock_interaction 1 ;# 启用时钟交互报告
set en_congestion 1 ;# 启用拥塞报告
set en_exceptions 1 ;# 启用时序例外报告
set en_util 1 ;# 启用资源利用率报告
set en_methodology 1 ;# 启用设计方法学报告
set en_qor 1 ;# 启用QOR报告
set en_failfast 1 ;# 启用快速失败分析
set en_high_fanout_nets 1 ;# 启用高扇出网络报告
set en_ram_util 1 ;# 启用RAM利用率报告
}
default {
puts "Invalid mode, should be 1, 2 or 3"
return -code 1
}
}
}
}
}
tcl/modules/report_utils.tcl
封装 report_failfast 命令的健壮调用逻辑,自动探测本地 XilinxTclStore 存在性,若缺失则触发 git clone 下载,确保脚本在无预装环境下的可运行性。
package provide report_utils 1.0
proc ::xilinx::designutils::report_failfast {args} {
if {[info commands ::tclapp::xilinx::designutils::report_failfast] eq ""} {
if {[info exists ::script_dir]} {
set project_root [file normalize [file join $::script_dir ..]]
set candidate_paths [list \
[file normalize [file join $project_root XilinxTclStore/tclapp/xilinx/designutils]] \
]
foreach local_designutils_path $candidate_paths {
if {[file exists $local_designutils_path]} {
if {[lsearch -exact $::auto_path $local_designutils_path] == -1} {
lappend ::auto_path $local_designutils_path
}
catch {package require ::tclapp::xilinx::designutils}
break
}
}
if {[info commands ::tclapp::xilinx::designutils::report_failfast] eq ""} {
# Use repo_utils to clone if needed
::repo_utils::ensure_tcl_store $project_root
set clone_root [file normalize [file join $project_root XilinxTclStore]]
set designutils_path [file normalize [file join $clone_root tclapp/xilinx/designutils]]
if {[file exists $designutils_path]} {
if {[lsearch -exact $::auto_path $designutils_path] == -1} {
lappend ::auto_path $designutils_path
}
catch {package require ::tclapp::xilinx::designutils}
}
}
}
}
if {[info commands ::tclapp::xilinx::designutils::report_failfast] ne ""} {
tailcall ::tclapp::xilinx::designutils::report_failfast {*}$args
}
puts "report_failfast is disabled or designutils package is not available"
return -code ok
}
tcl/modules/repo_utils.tcl
提供 XilinxTclStore 仓库的按需克隆能力,通过 git clone --depth 1 实现轻量拉取,是 report_utils 模块的底层依赖。
package provide repo_utils 1.0
namespace eval ::repo_utils {
variable script_dir [file dirname [file normalize [info script]]]
# Clone XilinxTclStore if needed
proc ensure_tcl_store {project_root} {
set clone_root [file normalize [file join $project_root XilinxTclStore]]
# Check if directory exists and is not empty
set need_clone 0
if {![file exists $clone_root]} {
set need_clone 1
} else {
# Check if empty
if {[llength [glob -nocomplain -directory $clone_root *]] == 0} {
set need_clone 1
# If empty directory exists, we might need to remove it for git clone to work easily,
# or just clone into it (git allows cloning into empty dir).
}
}
if {$need_clone} {
set git_bin git
if {[info exists ::env(GIT_BIN)] && $::env(GIT_BIN) ne ""} {
set git_bin $::env(GIT_BIN)
}
puts "Cloning XilinxTclStore to $clone_root..."
if {[catch {exec $git_bin clone --depth 1 https://github.com/Xilinx/XilinxTclStore.git $clone_root} err]} {
puts "Warning: failed to clone XilinxTclStore: $err"
return 0
} else {
puts "Cloned XilinxTclStore into $clone_root"
return 1
}
}
return 1
}
}
tcl/modules/slr_analysis.tcl
专为多 SLR(Super Logic Region)架构设计,分析跨 SLR 网络的发送/接收单元及其 BEL 映射,识别未使用 Laguna 寄存器的跨域路径,并生成高逻辑级数(LL)与高扇出(FO)路径报告。
#########################################################################################
## SLR analysis functions
#########################################################################################
namespace eval timing_analysis {
# 生成单元和BEL对应关系的CSV报告
# 参数: fn - 文件名前缀,用于生成最终的CSV文件名
# cell - 单元列表,包含需要报告的单元
# bel - 对应的BEL列表,与cell列表一一对应
# 返回值: 无,直接生成CSV文件
# 功能: 将每个单元及其对应的BEL名称写入CSV文件,便于分析单元在FPGA中的物理位置
proc report_cell_bel {fn cell bel} {
set fid [open ${fn}.csv w]
puts $fid "#\n# File created on [clock format [clock seconds]] \n#\n"
puts $fid "CellName, BelName"
foreach i_cell $cell i_bel $bel {
puts $fid "$i_cell, $i_bel"
}
close $fid
puts "Cells and their bels are recorded"
}
# 获取没有使用Laguna寄存器的单元并生成报告
# 参数: sn - 序列号,用于标识报告文件和日志信息
# xnets - 网络列表,包含跨SLR的网络
# en - 使能标志,1表示生成报告,0表示跳过
# 返回值: 无,直接生成报告文件
# 功能: 分析跨SLR网络的发送和接收单元,筛选出没有使用Laguna寄存器的单元,并生成详细报告
proc get_cells_not_use_laguna {sn xnets {en 1}} {
if {$en==1} {
puts "Get cells without using laguna registers"
set tx_cells [get_cells -of [get_pins -of [get_nets $xnets] -leaf -filter "DIRECTION==OUT"]]
set rx_cells [get_cells -of [get_pins -of [get_nets $xnets] -leaf -filter "DIRECTION==IN"]]
set tx_bels [get_bels -of $tx_cells]
set rx_bels [get_bels -of $rx_cells]
set tx_nlaguna_bels [filter $tx_bels "NAME !~ LAGUNA*"]
set tx_laguna_bels [filter $tx_bels "NAME =~ LAGUNA*"]
set rx_nlaguna_bels [filter $rx_bels "NAME !~ LAGUNA*"]
set rx_laguna_bels [filter $rx_bels "NAME =~ LAGUNA*"]
if {[llength $tx_cells]>0} {
set fn ${sn}_tx_crossing_slr_analysis
show_objects $tx_cells -name $fn
report_cell_bel $fn $tx_cells $tx_bels
}
if {[llength $rx_cells]>0} {
set fn ${sn}_rx_crossing_slr_analysis
show_objects $rx_cells -name $fn
report_cell_bel $fn $rx_cells $rx_bels
}
puts "Complete: get cells and their bels crossing SLRs"
}
}
# 报告跨SLR的时序路径
# 参数: sn - 序列号,用于标识报告文件和日志信息
# dcp_type - DCP类型,3/4表示布局DCP,5/6表示布线DCP
# en_get_cells_crossing_slrs - 是否获取跨SLR单元,1表示获取,0表示不获取
# en - 使能标志,1表示生成报告,0表示跳过
# 返回值: 无,直接生成跨SLR时序路径报告
# 功能: 根据DCP类型获取跨SLR网络,分析通过这些网络的时序路径,筛选出逻辑级别过高或扇出过大的路径,并生成详细报告
proc report_paths_crossing_slrs {sn dcp_type en_get_cells_crossing_slrs {en 1}} {
set place_dcp {3 4}
set route_dcp {5 6}
if {$en==1} {
if {$dcp_type in $place_dcp} {
set xneta [xilinx::designutils::get_inter_slr_nets]
set xnets [filter $xneta "TYPE != GLOBAL_CLOCK"]
} elseif {$dcp_type in $route_dcp} {
set xnets [xilinx::designutils::get_sll_nets]
} else {
puts "The nets crossing SLRs cannot be obtained under this DCP"
return
}
set xnets_len [llength $xnets]
if {$xnets_len>0} {
set paths [get_timing_paths -nworst 1 -max $xnets_len -through $xnets \
-filter {INTER_SLR_COMPENSATION != ""}]
get_cells_not_use_laguna $sn $xnets $en_get_cells_crossing_slrs
}
if {[llength $paths]>0} {
set paths_ll_1 [filter $paths "LOGIC_LEVELS > 0"]
set paths_fo_1 [filter $paths "MAX_FANOUT > 1"]
if {[llength $paths_ll_1]} {
set fn ${sn}_paths_crossing_slr_LL_violation
report_timing -of $paths_ll_1 -name $fn
create_timing_report $fn $paths_ll_1
}
if {[llength $paths_fo_1]} {
set fn ${sn}_paths_crossing_slr_FO_violation
report_timing -of $paths_fo_1 -name $fn
create_timing_report $fn $paths_fo_1
}
}
puts "Complete section $sn: report_paths_crossing_slrs"
}
}
}
tcl/modules/slr_utils.tcl
提供 get_inter_slr_nets 和 get_sll_nets 两个核心命令,前者用于布局阶段(post_place)提取跨 SLR 网络,后者用于布线阶段(post_route)提取 SLL(SLR Link Layer)节点关联网络,是跨域分析的数据源头。
package provide slr_utils 1.0
namespace eval ::xilinx::designutils {}
proc ::xilinx::designutils::lshift {inputlist} {
upvar $inputlist argv
set arg [lindex $argv 0]
set argv [lrange $argv 1 end]
return $arg
}
proc ::xilinx::designutils::get_inter_slr_nets {args} {
set error 0
set help 0
set from {}
set to {}
while {[llength $args]} {
set name [::xilinx::designutils::lshift args]
switch -regexp -- $name {
-from -
{^-f(r(om?)?)?$} {
set from [string toupper [::xilinx::designutils::lshift args]]
}
-to -
{^-to?$} {
set to [string toupper [::xilinx::designutils::lshift args]]
}
-usage -
{^-u(s(a(ge?)?)?)?$} {
set help 1
}
default {
if {[string match "-*" $name]} {
incr error
} else {
incr error
}
}
}
}
if ($help) {
return {}
}
if {($from == {} && $to != {}) || ($from != {} && $to == {})} {
incr error
} elseif {$from != {} && $to != {}} {
set SLRs [get_slrs -quiet]
if {[lsearch $SLRs $from] == -1} {
incr error
}
if {[lsearch $SLRs $to] == -1} {
incr error
}
if {($error == 0) && ($from == $to)} {
incr error
}
}
if {$error} {
error "get_inter_slr_nets: invalid arguments"
}
set inter_slr_nets [list]
if {($from == {}) && ($to == {})} {
catch {unset ar}
foreach SLR [get_slrs -quiet] {
set cells [get_cells -quiet -of [get_sites -quiet -of $SLR]]
set nets [get_nets -quiet -of $cells -filter {(TYPE != POWER) && (TYPE != GROUND) && (ROUTE_STATUS != INTRASITE)}]
set props [lsort -uniq [get_property -quiet PARENT $nets]]
foreach prop $props { lappend ar($prop) $SLR }
}
set slrs [list]
foreach net [array names ar] {
if {[llength $ar($net)] > 1} {
lappend slrs $net
}
}
set inter_slr_nets [get_nets -quiet $slrs]
return $inter_slr_nets
}
catch {unset ar}
set fromSlr $from
set toSlr $to
set from_cells [get_cells -quiet -of [get_sites -quiet -of [get_slrs -quiet $fromSlr]]]
set from_nets [get_nets -quiet -of $from_cells -filter {(TYPE != POWER) && (TYPE != GROUND) && (ROUTE_STATUS != INTRASITE)}]
set from_props [lsort -uniq [get_property -quiet parent $from_nets]]
foreach prop $from_props { lappend ar($prop) $fromSlr }
set to_cells [get_cells -quiet -of [get_sites -quiet -of [get_slrs -quiet $toSlr]]]
set to_nets [get_nets -quiet -of $to_cells -filter {(TYPE != POWER) && (TYPE != GROUND) && (ROUTE_STATUS != INTRASITE)}]
set to_props [lsort -uniq [get_property -quiet parent $to_nets]]
foreach prop $to_props { lappend ar($prop) $toSlr }
set slrs [list]
foreach net [array names ar] {
if {[llength $ar($net)] > 1} {
lappend slrs $net
}
}
set inter_slr_nets [get_nets -quiet $slrs]
return $inter_slr_nets
}
proc ::xilinx::designutils::get_sll_nets {} {
set sll_nodes [get_nodes -quiet -of_objects [get_tiles -quiet -of_objects [get_slrs -quiet ]] -filter {IS_CROSSING_SLRS}]
if {[llength $sll_nodes] == 0} {
return {}
}
return [get_nets -quiet -of $sll_nodes]
}
tcl/modules/timing_checks.tcl
封装标准 Vivado 时序检查命令,包括 check_timing、report_timing_summary、get_timing_paths(负裕量路径)、report_block2block 及 report_high_logic_level,构成时序收敛闭环的核心环节。
#########################################################################################
## Timing analysis functions
#########################################################################################
namespace eval timing_analysis {
# 执行时序检查并生成报告
proc my_check_timing {sn {en 1}} {
if {$en==1} {
puts "Start section $sn: check_timing"
check_timing -name ${sn}_check_timing_analysis \
-file ${sn}_check_timing_analysis.rpt -verbose
puts "Complete section $sn: check_timing"
}
}
# 根据DCP类型生成时序摘要报告
proc my_timing_summary {sn dcp_type {en 1}} {
set syn_dcp {1 2}
if {$en==1} {
puts "Start section $sn: report_timing_summary"
if {$dcp_type in $syn_dcp} {
report_timing_summary -no_check_timing -no_header -setup -max_paths 50 -nworst 1 -unique_pins \
-name ${sn}_timing_summary_analysis -file ${sn}_timing_summary_analysis.rpt
} else {
report_timing_summary -no_check_timing -no_header -max_paths 50 -nworst 1 -unique_pins \
-name ${sn}_timing_summary_analysis -file ${sn}_timing_summary_analysis.rpt
}
puts "Complete section $sn: report_timing_summary"
}
}
# 生成负时序裕量路径报告
proc my_neg_slack_timing_report {sn dcp_type {en 1}} {
set syn_dcp {1 2}
if {$en==1} {
puts "Start section $sn: report_timing: 100 negative slack paths"
if {$dcp_type in $syn_dcp} {
set paths [get_timing_paths -setup -max_paths 100 -nworst 1 -unique_pins \
-slack_lesser_than 0 -quiet]
} else {
set paths [get_timing_paths -delay_type min_max -max_paths 100 -nworst 1 \
-unique_pins -slack_lesser_than 0 -quiet]
}
if {[llength $paths]>0} {
set fn ${sn}_neg_slack_path_violation
report_timing -of $paths -name $fn
create_timing_report $fn $paths
puts "Complete section $sn: report_timing: 100 negative slack paths"
} else {
puts "There is no any paths with negative slack"
}
}
}
# 生成块到块的时序路径报告
proc report_block2block {sn block {en 1}} {
if {$en==1} {
puts "Start section $sn: report_block2block: 100 paths from blocks to blocks"
set fn ${sn}_b2b_violation
set paths_block2block [get_timing_paths -from $block -to $block -max_paths 100 -quiet]
if {[llength $paths_block2block]>0} {
report_timing -of $paths_block2block -name $fn
create_timing_report $fn $paths_block2block
puts "Complete section $sn: report_block2block: 100 paths from blocks to blocks"
} else {
puts "There is not any path from blocks to blocks"
}
}
}
# 报告在给定时钟下逻辑级别过高的路径
proc report_high_logic_level {sn clock_name delay {en 1}} {
if {$en==1} {
puts "Start section $sn: report_high_logic_level: 10 paths with higher logic level under given clock"
set clock_period [get_property PERIOD $clock_name]
set clock_freq [format "%.2f" [expr 1000.0/$clock_period]]
set max_ll [expr int(floor(double($clock_period)/double($delay)))]
set paths [get_timing_paths -max_paths 10 -filter "GROUP==$clock_name && LOGIC_LEVELS>$max_ll" -quiet]
if {[llength $paths]>0} {
set fn ${sn}_${clock_name}_${clock_freq}_LL_${max_ll}
report_timing -of $paths -name $fn
create_timing_report $fn $paths
}
puts "Complete section $sn: report_high_logic_level: $clock_name $clock_freq $max_ll 10 paths"
}
}
}
tcl/modules/yosys_flow.tcl
提供 Yosys 合成流程集成能力,当 Vivado 不可用时,自动读取 Verilog 文件、生成 .ys 脚本、调用 yosys 并捕获关键路径延迟,实现跨工具链的时序初步评估。
package provide yosys_flow 1.0
source [file join [file dirname [info script]] "repo_utils.tcl"]
namespace eval ::yosys_flow {
proc run_flow {script_dir} {
# Clone XilinxTclStore if needed (using repo_utils)
set project_root [file normalize [file join $script_dir ..]]
::repo_utils::ensure_tcl_store $project_root
# Access configuration variables from the caller's scope
uplevel 1 {
# 记录项目根目录,以便处理相对路径
set project_dir [pwd]
# 提前创建并进入 Report 目录,确保所有输出都在此目录下
file mkdir Report
cd ./Report
if {![info exists yosys_bin]} {set yosys_bin yosys}
if {![info exists yosys_script]} {set yosys_script ""}
if {$yosys_script eq ""} {
# Try to auto-generate Yosys script if verilog files and top module are provided
if {[info exists yosys_verilog_files] && [info exists yosys_top_module] && [llength $yosys_verilog_files] > 0 && $yosys_top_module ne ""} {
set yosys_script "auto_generated.ys"
set filelist_name "auto_generated.f"
set yfid [open $yosys_script w]
set ffid [open $filelist_name w]
# 检查输入是否为 filelist 文件 (.f, .list, .flist)
set raw_files $yosys_verilog_files
if {[llength $raw_files] == 1} {
set fpath [lindex $raw_files 0]
if {[file pathtype $fpath] eq "relative"} { set fpath_abs [file join $project_dir $fpath] } else { set fpath_abs $fpath }
if {[file exists $fpath_abs]} {
set ext [file extension $fpath_abs]
if {$ext eq ".f" || $ext eq ".list" || $ext eq ".flist"} {
set raw_files [list]
if {[catch {open $fpath_abs r} ifid] == 0} {
while {[gets $ifid line] >= 0} {
set line [string trim $line]
# 忽略空行和注释
if {$line ne "" && ![string match "#*" $line] && ![string match "//*" $line]} {
lappend raw_files $line
}
}
close $ifid
}
}
}
}
foreach vfile $raw_files {
# 处理 Verilog 文件路径:如果是相对路径,加上 project_dir
if {[file pathtype $vfile] eq "relative"} {
set vfile_abs [file join $project_dir $vfile]
} else {
set vfile_abs $vfile
}
puts $yfid "read_verilog $vfile_abs"
puts $ffid "$vfile_abs"
}
close $ffid
puts "Auto-generated filelist: [file join [pwd] $filelist_name]"
if {![info exists yosys_target]} {
set yosys_target "generic"
}
switch -- $yosys_target {
xilinx {
set synth_cmd "synth_xilinx -top $yosys_top_module"
if {[info exists yosys_device]} {
set dev [string tolower $yosys_device]
if {[string match "*ku5p*" $dev]} {
append synth_cmd " -family xcup"
} elseif {[string match "*vu9p*" $dev]} {
append synth_cmd " -family xcup"
} elseif {[string match "*k7325t*" $dev]} {
append synth_cmd " -family xc7"
} elseif {[string match "*zynq7020*" $dev]} {
append synth_cmd " -family xc7"
}
}
puts $yfid $synth_cmd
}
altera {
puts $yfid "synth_intel -top $yosys_top_module"
}
default {
puts $yfid "synth -top $yosys_top_module"
}
}
if {[info exists yosys_liberty_file] && $yosys_liberty_file ne ""} {
# 处理 Liberty 文件路径
if {[file pathtype $yosys_liberty_file] eq "relative"} {
set lib_abs [file join $project_dir $yosys_liberty_file]
} else {
set lib_abs $yosys_liberty_file
}
puts $yfid "dfflibmap -liberty $lib_abs"
puts $yfid "abc -liberty $lib_abs"
puts $yfid "stat -liberty $lib_abs"
} else {
puts $yfid "stat"
}
close $yfid
puts "Auto-generated Yosys script: [file join [pwd] $yosys_script]"
} else {
puts "yosys_script is not set; please set it to a Yosys .ys file, or provide yosys_verilog_files and yosys_top_module to auto-generate."
return -code 1
}
} else {
# 如果用户提供了脚本,且是相对路径,需要修正为绝对路径(因为我们已经 cd 到了 Report)
if {[file pathtype $yosys_script] eq "relative"} {
set yosys_script [file join $project_dir $yosys_script]
}
}
set yosys_cmd [list $yosys_bin -s $yosys_script]
if {[catch {set yosys_output [eval exec $yosys_cmd]} err]} {
set fid [open yosys_timing.log w]
puts $fid $err
close $fid
puts "Error when running Yosys; see Report/yosys_timing.log"
set yosys_output ""
}
set fid [open yosys_timing.log w]
puts $fid $yosys_output
close $fid
set worst_line ""
foreach line [split $yosys_output "\n"] {
if {[string match "*Longest path*" $line]} {
set worst_line $line
}
}
if {$worst_line ne ""} {
set sfid [open yosys_timing_summary.txt w]
puts $sfid $worst_line
close $sfid
}
}
}
}
tcl/test/test_loader.tcl
配置加载器的单元测试脚本,验证 JSON/YAML 解析器能否正确读取 config.json 或 config.yaml,并提取关键字段如 vivado_version 和 steps.user_check_timing。
set script_dir [file dirname [file normalize [info script]]]
source [file join [file dirname $script_dir] "modules/config_loader.tcl"]
set root_dir [file dirname [file dirname $script_dir]]
set json_file [file join $root_dir "config/config.json"]
if {![file exists $json_file]} {
set json_file [file join $root_dir "config.json"]
}
set yaml_file [file join $root_dir "config/config.yaml"]
if {![file exists $yaml_file]} {
set yaml_file [file join $root_dir "config.yaml"]
}
puts "Testing JSON Loader..."
if {[catch {
set cfg [::config_loader::load_config $json_file]
puts "JSON Loaded Successfully:"
puts "Content: $cfg"
puts " vivado_version: [dict get $cfg vivado_version]"
puts " steps.user_check_timing: [dict get $cfg steps user_check_timing]"
} err]} {
puts "JSON Failed: $err"
}
puts "\nTesting YAML Loader..."
if {[catch {
set cfg [::config_loader::load_config $yaml_file]
puts "YAML Loaded Successfully:"
puts " vivado_version: [dict get $cfg vivado_version]"
puts " steps.user_check_timing: [dict get $cfg steps user_check_timing]"
} err]} {
puts "YAML Failed: $err"
}
tcl/test/test_yosys_gen.tcl
Yosys 脚本生成测试,演示如何通过 Tcl 变量(yosys_verilog_files, yosys_top_module)驱动 yosys_flow 模块,生成可执行的 .ys 合成脚本。
set yosys_bin "yosys"
# 方式 1: 直接提供 Verilog 文件列表
set yosys_verilog_files [list "test/test.v"]
# 方式 2: 提供一个文件列表文件 (filelist)
# set yosys_verilog_files [list "src/filelist.f"]
set yosys_top_module "top"
set yosys_liberty_file ""
if {[info exists ::env(YOSYS_LIB)] && $::env(YOSYS_LIB) ne ""} {
set yosys_liberty_file $::env(YOSYS_LIB)
}
set yosys_target "generic"
if {[info exists ::env(YOSYS_TARGET)] && $::env(YOSYS_TARGET) ne ""} {
set yosys_target $::env(YOSYS_TARGET)
}
set yosys_device ""
if {[info exists ::env(YOSYS_DEVICE)] && $::env(YOSYS_DEVICE) ne ""} {
set yosys_device $::env(YOSYS_DEVICE)
}
set script_dir [file normalize [file dirname [info script]]]
source [file join $script_dir "../timing_analysis.tcl"]
tcl/test/verify_modules.tcl
模块加载完整性验证脚本,确保 slr_utils、report_utils、params_utils 等关键命名空间中的函数均已成功注册,防止因路径错误或依赖缺失导致运行时异常。
set script_dir [file normalize [file join [file dirname [info script]] "../"]]
source [file join $script_dir "modules/slr_utils.tcl"]
source [file join $script_dir "modules/report_utils.tcl"]
source [file join $script_dir "modules/params_utils.tcl"]
set failed 0
if {[info commands ::xilinx::designutils::lshift] eq ""} {
puts "Error: ::xilinx::designutils::lshift not defined"
set failed 1
}
if {[info commands ::xilinx::designutils::report_failfast] eq ""} {
puts "Error: ::xilinx::designutils::report_failfast not defined"
set failed 1
}
if {[info commands ::params_utils::set_defaults] eq ""} {
puts "Error: ::params_utils::set_defaults not defined"
set failed 1
}
if {$failed == 0} {
puts "All modules loaded successfully."
} else {
puts "Module verification failed."
exit 1
}
tcl/timing_analysis.tcl
主入口脚本,负责整体流程编排:加载配置 → 初始化参数 → 加载全部模块 → 打开 DCP → 按顺序调用各分析函数。其设计体现了典型的 网络/系统 层面的分层抽象思想——将复杂时序分析任务解耦为可插拔、可配置的原子模块。
#########################################################################################
## Author : yufl935
## File : timing_analysis.tcl
## Company : Xilinx
## Description : 时序分析工具主入口脚本 (Main Entry Point)
## 负责配置加载、模块初始化及核心分析流程的调度。
## 实际业务逻辑已分散至 tcl/modules/ 下的各独立模块中。
## 使用步骤如下:
#########################################################################################
# Step 1: 安装Design Utilities工具
#-------> Tools -> Xilinx Tcl Store -> Design Utilities
########################################################
# Step 2: 设置参数 (或使用 config.json/yaml)
#-------> vivado_version
# 版本格式应为dddd.d,如2018.2
# 注意: 2018.2.1格式不被接受
#-------> dcp_type
# 取值范围:
# 1 => 综合后 (post_synth)
# 2 => 优化后 (post_opt)
# 3 => 布局后 (post_place)
# 4 => 物理优化后(布局后) (post_phys_opt after place_design)
# 5 => 布线后 (post_route)
# 6 => 物理优化后(布线后) (post_phys_opt after route_design)
#-------> mode
# 1 => 用户定义模式
# 2 => 默认模式
# 3 => 完整模式
#-------> dcp_is_open
# 1 => DCP文件已打开
# 0 => DCP文件未打开,需要先打开DCP
########################################################
#########################################################################################
# 设置总开始时间,用于计算整个脚本执行时间
set total_start_time [clock format [clock seconds] -format "%s"]
set script_dir [file normalize [file dirname [info script]]]
# Load Config Loader
source [file join $script_dir "modules/config_loader.tcl"]
# 1. Load Configuration
# Priority: env(CONFIG_FILE) > config.json > config/config.json > config.yaml > config/config.yaml
set config_file ""
if {[info exists ::env(CONFIG_FILE)] && [file exists $::env(CONFIG_FILE)]} {
set config_file $::env(CONFIG_FILE)
}
if {$config_file eq ""} {
foreach fname {"config.json" "config/config.json" "config.yaml" "config/config.yaml"} {
set fpath [file join $script_dir $fname]
if {[file exists $fpath]} {
set config_file $fpath
break
}
# Also check relative to CWD if script_dir logic fails or is different
if {[file exists $fname]} {
set config_file $fname
break
}
}
}
if {$config_file eq ""} {
puts "Error: No configuration file found (checked config.json, config/config.json, .yaml)"
return -code 1
}
puts "Loading configuration from $config_file..."
set cfg [::config_loader::load_config $config_file]
# Apply top-level keys to global variables
dict for {k v} $cfg {
if {$k ne "steps"} {
set $k $v
}
}
# Apply steps keys
if {[dict exists $cfg steps]} {
dict for {k v} [dict get $cfg steps] {
set $k $v
}
}
# Source new modules
source [file join $script_dir "modules/repo_utils.tcl"]
source [file join $script_dir "modules/yosys_flow.tcl"]
source [file join $script_dir "modules/slr_utils.tcl"]
source [file join $script_dir "modules/report_utils.tcl"]
source [file join $script_dir "modules/params_utils.tcl"]
if {[llength [info commands get_timing_paths]] == 0} {
# Run Yosys flow if Vivado commands are not available
::yosys_flow::run_flow $script_dir
return
}
# Source existing modules
source $script_dir/modules/common_utils.tcl
source $script_dir/modules/csv_reporters.tcl
source $script_dir/modules/timing_checks.tcl
source $script_dir/modules/slr_analysis.tcl
source $script_dir/modules/bram_uram_analysis.tcl
source $script_dir/modules/dsp_analysis.tcl
source $script_dir/modules/cell_analysis.tcl
source $script_dir/modules/design_analysis.tcl
# Set default parameters
::params_utils::set_defaults
#########################################################################################
# 打开DCP文件
if {$dcp_is_open==0} {
# 使用glob命令查找符合模式的DCP文件
set target_dcp [glob -nocomplain $dcp_name]
# 获取找到的DCP文件数量
set dcp_num [llength $target_dcp]
# 检查DCP文件数量是否合法
if {$dcp_num==0} {
puts "当前工作目录中没有找到DCP文件"
return -code 1
} elseif {$dcp_num>1} {
puts "找到多个DCP文件,无法确定使用哪个"
return -code 1
} else {
# 记录打开DCP文件的开始时间
set open_dcp_start_time [clock format [clock seconds] -format "%s"]
# 打开DCP文件
open_checkpoint $target_dcp
# 记录打开DCP文件的结束时间
set open_dcp_end_time [clock format [clock seconds] -format "%s"]
}
}
#########################################################################################
# 创建Report目录用于存放报告文件
if {![info exists report_dir]} {
set report_dir [file join [pwd] Report]
}
file mkdir $report_dir
# 切换到Report目录
cd $report_dir
# Check DCP type and Mode
::params_utils::check_dcp_and_mode
#########################################################################################
# 根据选择的检查项执行相应的分析
#########################################################################################
set index 1
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_check_timing $sn $en_check_timing
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_timing_summary $sn $dcp_type $en_timing_summary
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_neg_slack_timing_report $sn $dcp_type $en_neg_slack_timing
incr index
set sn [timing_analysis::get_sn $index $phase]
set block [get_cells -hierarchical -filter "IS_PRIMITIVE==0 && IS_COMPOSITE==0 && REF_NAME!=IBUF && REF_NAME!=OBUF" -quiet]
timing_analysis::report_block2block $sn $block $en_block2block
set clock_list [get_clocks -quiet]
foreach clock_name $clock_list {
incr index
set sn [timing_analysis::get_sn $index $phase]
set period [get_property PERIOD $clock_name]
set delay [timing_analysis::get_timing_base [list $dcp_type -1] $is_accurate]
timing_analysis::report_high_logic_level $sn $clock_name $delay $en_high_logic_level
}
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::report_paths_crossing_slrs $sn $dcp_type $en_get_cells_crossing_slrs $en_crossing_slrs
set used_bram [get_cells -hierarchical -filter "REF_NAME =~ RAMB18* || REF_NAME =~ RAMB36* || REF_NAME =~ FIFO18* || REF_NAME =~ FIFO36*" -quiet]
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_no_reg_bram $sn $used_bram $en_check_bram
set used_uram [get_cells -hierarchical -filter "REF_NAME =~ URAM*" -quiet]
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_no_reg_uram $sn $used_uram $en_check_uram
set used_dsp [get_cells -hierarchical -filter "REF_NAME =~ DSP*" -quiet]
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_no_mreg_dsp $sn $used_dsp $en_check_dsp_mreg
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_no_preg_dsp $sn $used_dsp $en_check_dsp_preg
set lower_srl [get_cells -hierarchical -filter "REF_NAME =~ SRL16* || REF_NAME =~ SRLC32*" -quiet]
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_lower_depth_srl $sn $lower_srl $en_check_lower_srl
set combined_lut [get_cells -hierarchical -filter "REF_NAME =~ LUT* && IS_COMBINED==1" -quiet]
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_combined_lut $sn $combined_lut $en_check_combined_lut
set muxfx_cells [get_cells -hierarchical -filter "REF_NAME =~ MUXF7 || REF_NAME =~ MUXF8 || REF_NAME =~ MUXF9" -quiet]
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_muxfx $sn $muxfx_cells $en_check_muxfx
set latch_cells [get_cells -hierarchical -filter "IS_LATCH==1" -quiet]
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::get_latch $sn $latch_cells $en_check_latch
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::report_clock_characteristics $sn $en_clock_characteristics
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_clock_networks $sn $en_clock_networks
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_report_cdc $sn $en_cdc
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_clock_interaction $sn $en_clock_interaction
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::report_congestion_level $sn $en_congestion
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_report_exceptions $sn $en_exceptions
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_report_utilization $sn $en_util
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_report_methodology $sn $en_methodology
incr index
set sn [timing_analysis::get_sn $index $phase]
if {$en_qor} {
report_qor_suggestions -name ${sn}_qor_suggestions_analysis -file ${sn}_qor_suggestions_analysis.rpt
}
incr index
set sn [timing_analysis::get_sn $index $phase]
set slrs [expr {[llength [get_slrs -quiet]] == 1 ? 1 : 0}]
timing_analysis::report_failfast $sn $slrs $dcp_type $en_failfast
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::high_fanout_nets $sn $slrs $dcp_type $en_high_fanout_nets
incr index
set sn [timing_analysis::get_sn $index $phase]
timing_analysis::my_ram_util $sn $en_ram_util
#########################################################################################
# 打印脚本执行时间
#########################################################################################
set total_end_time [clock format [clock seconds] -format "%s"]
set total_exec_time [expr $total_end_time - $total_start_time]
puts "Total execution time: $total_exec_time seconds"
配置文件
config/config.json
{
"vivado_version": "2022.2",
"dcp_type": 5,
"dcp_name": "*routed.dcp",
"report_dir": "Report",
"yosys_verilog_files": ["XilinxTclStore/tclapp/xilinx/xsim/test/src/top.v"],
"yosys_top_module": "top",
"steps": {
"user_check_timing": true,
"user_timing_summary": true,
"user_neg_slack_timing": true,
"user_qor": false,
"user_failfast": false
}
}
config/config.yaml
# Vivado Configuration
vivado_bin: vivado
dcp_dir: ""
dcp_name: "*routed.dcp"
vivado_version: "2022.2"
dcp_type: 5
mode: 1
# Yosys Configuration
yosys_bin: yosys
yosys_verilog_files: ["XilinxTclStore/tclapp/xilinx/xsim/test/src/top.v"] # Example: ["/path/to/design.v", "/path/to/utils.v"]
yosys_top_module: top
# Output
report_dir: Report
# Analysis Steps (true/false)
steps:
user_check_timing: true
user_timing_summary: true
user_neg_slack_timing: true
user_block2block: true
user_high_logic_level: true
user_crossing_slrs: false
user_get_cells_crossing_slrs: false
user_check_bram: true
user_check_uram: true
user_check_dsp_mreg: true
user_check_dsp_preg: true
user_check_lower_srl: true
user_check_combined_lut: true
user_check_muxfx: true
user_check_latch: true
user_clock_characteristics: true
user_clock_networks: true
user_cdc: true
user_clock_interaction: true
user_congestion: true
user_exceptions: true
user_util: true
user_methodology: true
user_qor: false
user_failfast: false
user_high_fanout_nets: true
user_ram_util: true
构建与 Git 配置
.gitmodules
# Xilinx Tcl Store 仓库地址
# 用于 Vivado 或 Vitis 的 Tcl 脚本扩展仓库
[submodule "XilinxTclStore"]
path = XilinxTclStore
url = https://github.com/Xilinx/XilinxTclStore.git
shallow = true
.gitignore
*Script*
Report/
auto_generated.ys
XilinxTclStore
.vscode/
Makefile
XILINXTA_DIR := $(CURDIR)
REPORT_DIR ?= Report
TCLSH ?= tclsh
VIVADO ?= vivado
YOSYS ?= yosys
.PHONY: all timing yosys_gen yosys_run config_test vivado_batch clean distclean help
all: module_test config_test yosys_test timing
help:
@echo "XilinxTimingAnalysis Makefile 使用说明"
@echo " make # 模块验证 + 配置验证 + Yosys集成测试 + 生成自动脚本"
@echo " make module_test # 验证新模块加载与核心函数"
@echo " make config_test # 验证 JSON/YAML 配置解析 (支持 config/ 目录)"
@echo " make yosys_test # 验证 Yosys 脚本生成流程 (使用 test/test_yosys_gen.tcl)"
@echo " make timing # 运行主脚本(非 Vivado 环境下生成 Report/auto_generated.ys/.f)"
@echo " make yosys_run # 运行 Yosys 使用 Report/auto_generated.ys"
@echo " make vivado_batch DCP_DIR=/path/to/project # Vivado 批处理模式执行"
@echo " make clean # 清理生成物"
@echo " make distclean # 深度清理(含测试输出)"
module_test:
$(TCLSH) "$(XILINXTA_DIR)/tcl/test/verify_modules.tcl"
config_test:
$(TCLSH) "$(XILINXTA_DIR)/tcl/test/test_loader.tcl"
yosys_test:
$(TCLSH) "$(XILINXTA_DIR)/tcl/test/test_yosys_gen.tcl"
timing:
$(TCLSH) "$(XILINXTA_DIR)/tcl/timing_analysis.tcl" || true
yosys_gen: timing
yosys_run:
@if [ ! -f "$(REPORT_DIR)/auto_generated.ys" ]; then $(TCLSH) "$(XILINXTA_DIR)/tcl/timing_analysis.tcl"; fi; \
$(YOSYS) -s "$(REPORT_DIR)/auto_generated.ys" || true
vivado_batch:
@if [ -z "$(DCP_DIR)" ]; then \
echo "DCP_DIR is not set"; \
exit 1; \
fi; \
cd "$(DCP_DIR)" && $(VIVADO) -mode batch -source "$(XILINXTA_DIR)/tcl/timing_analysis.tcl"
clean:
rm -rf "$(REPORT_DIR)" auto_generated.ys auto_generated.f yosys_timing.log
distclean: clean
rm -rf tcl/test/output