在vivado中使用tcl脚本(UG894)

您所在的位置:网站首页 vivado怎么生成bitstream 在vivado中使用tcl脚本(UG894)

在vivado中使用tcl脚本(UG894)

2023-08-29 23:41| 来源: 网络整理| 查看: 265

本文源自UG894,主要介绍如何在vivado中使用tcl脚本

1.vivado中如何获取tcl help

vivado中任何自带的命令都可以通过“-help”获取帮助信息。 也可以直接输入“help”取得vivado命令合集,并通过“help -category (tools)”来获取某类操作的所有命令简介。

2.工程模式下编译和报告示例脚本

该过程可以通过运行GUI vivado自动产生的记录文件vivado.jou查看,该文件中记录了自打开vivado后运行的所有命令。 1.通过create_project命令建立工程。 2.通过add_files或import_files添加工程文件。 3.通过launch_runs和wait_on_run依次进行综合实现。

3.在vivado中加载并运行自己的tcl脚本 3.1 vivado启动时自动初始化脚本

vivado会在启动时自动加载Vivado_init.tcl文件中定义的脚本。可将自己写的proc写入该文件中,或者直接source自己的tcl脚本。 Vivado_inti.tcl路径为%APPDATA%/Xilinx/Vivado//Vivado_init.tcl

3.2 通过source运行tcl脚本

可以通过source将tcl脚本加载进入vivado中

source

使用source会默认打印脚本代码内容,可通过“-notrace”隐藏代码内容

source -notrace 3.3 在vivado编译流程中添加脚本

在Setting中,找到综合、实现、生成比特流设置,都可以添加tcl.pre和tcl.post的路径,即可在该流程之前或者之后插入自己的tcl脚本。

4.tcl脚本编写方法

原文档这部分主要简介tcl脚本语法及经典问题,此处只挑出其中的经典问题进行记录

4.1 proc参数数量不定时的解决方法

当proc参数数量不定时,即proc有0-n个参数都可以正常运行时,参数可以以链表形式给出,在proc中,依次对输入参数列表元素进行分析处理,每处理一个就删除一个,直至所有参数处理完毕。

# 将第一个元素从列表中移除 proc lshift listVar { upvar 1 $listVar L set r [lindex $L 0] set L [lreplace $L [set L 0] 0] return $r } proc myproc { args } { #------------------------------------------------------- # Process command line arguments #------------------------------------------------------- set error 0 set help 0 set verbose 0 set ports {} # if {[llength $args] == 0} { incr help }; # Uncomment if necessary # 以此分析参数元素,每次循环将第一个元素删除 while {[llength $args]} { set flag [lshift args] switch -exact -- $flag { -p - -ports { set ports [lshift args] } -v - -verbose { set verbose 1 } -h - -help { incr help } default { if {[string match "-*" $flag]} { puts " ERROR - option '$flag' is not a valid option." incr error } else { puts "ERROR - option '$flag' is not a valid option." incr error } } } } if {$help} { set callerflag [lindex [info level [expr [info level] -1]] 0] # return -code ok {} } # Check validity of arguments. Increment $error to generate an error if {$error} { return -code error {Oops, something is not correct} } # Do something return -code ok {} }

【注意】原文档中还给出了另一种用switch分析列表的方法,但是没有看懂,这里先不写了

4.2 局部变量和全局变量

局部变量是proc内部变量,该变量只在proc内使用,与外部变量相互独立(当与外部变量名字相同时,他们相互独立互不影响)。 全局变量是在proc外部定义的变量,属于全局命名空间变量。如果要在proc内部调用全局变量,需要在变量名前用global关键词;或者在变量名前加“::”。

proc printEnv {} { global env foreach var [lsort [array names env]] { puts " $var = $env($var)" } } proc printEnv {} { foreach var [lsort [array names ::env]] { puts " $var = $::env($var)" } }

【注意】通常不建议用在proc内用global调用全局变量,global通常用于避免向proc传递太大的列表。通常优先考虑用upvar。

4.3 namespace的使用

为了避免变量和函数名重复,可以将函数和变量定义在更独立的空间。命名空间可以嵌套,通过::来调用命名空间中的变量和函数。

01 namespace eval foo { 02 variable stack [list] 03 variable count 0 04 variable params 05 array set params [list var1 value1 var2 value2 var3 value3] 06 07 namespace export push pop 08 09 proc push { args } { 10 variable stack 11 variable count 12 lappend stack $args 13 incr count 14 } 15 16 proc pop {} { 17 variable stack 18 variable count 19 if {[llength $stack] > 0} { 20 set value [lindex $stack end] 21 set stack [lrange $stack 0 end-1] 22 incr count -1 23 return $value 24 } else { 25 error " no more element in the stack" 26 } 27 } 28 29 } 30 31 proc foo::dump {} { 32 variable stack 33 variable count 34 if {[llength $stack] > 0} { 35 puts " There are $count element(s) in the stack:" 36 foreach element $stack { 37 puts " $element" 38 } 39 return 0 40 } else { 41 error " no element in the stack" 42 } 43 } 44 45 namespace import foo::* 5.访问设计对象

vivado提供了非常丰富的get_*命令用于访问设计对象,这些命令会以列表形式返回所需内容。

5.1 对象特性

每种设计对象(net,pin,port……)都有不同种类的特性参数,可以通过get_* filter来找到某种特性的对象,可以通过report_property来展示某个对象的所有特性。特性也可以增加和删除。

5.2 对象检索

通过get_*、-hierarchical、filter、-regxp、-of_objects等来检索相关对象,此处不在赘述。

6.处理对象列表

通过get_*都以tcl链表形式返回对象,因此可以通过tcl内置的列表命令进行处理。

foreach X [lsort -decreasing [get_cells]] {puts $X} wbArbEngine usb_vbus_pad_1_i_IBUF_inst usb_vbus_pad_0_i_IBUF_inst usbEngine1 usbEngine0 ... 7.保存命令输出

很多vivado命令都支持将输出保存至指定文件中,通过-file选项;或将输出结果以string形式保存,通过-return_string,以便后面的函数调用。

# 新建文件并写入内容 report_timing -delay_type max -file setup_violations.rpt report_timing -delay_type min -file hold_violations.rpt # 通过-append在原有文件中新增内容 report_timing -delay_type max -file all_violations.rpt report_timing -delay_type min -file -append all_violations.rpt

文件的绝对或者相对路径可以当作文件名的一部分。

report_*命令也可以通过-return_string将结果以字符串形式输出,并可以赋给tcl变量。

set timeLines [split [report_timing -return_string -max_paths 10] \n ] 7.1 对文件进行操作

tcl提供了一系列命令对文件进行操作,这些都是tcl基本语法,此处不再赘述。

7.2 对字符串进行操作

tcl提供了一系列命令对字符串进行处理,此处不再赘述。

8.error处理

写脚本时,一个好的习惯:使用某个参数前先检查参数状态,比如打开文件前检查文件是否存在,使用报告列表时先检查是否有元素存在,这样会避免很多脚本运行错误。

8.1 检查变量状态 # 打开文件前检查文件是否存在 if {[file exists $filename]} { set FH [open $filename r] if {$FH != {}} { # The file is opened, do something # … close $FH } else { puts " File $filename could not be opened" } } else { puts " File $filename does not exist" } # 在使用get_*之后,检查对象是否存在 proc get_pin_dir { pinName } { if {$pinName == {}} { puts " Error - no pin name provided" return {} } set pin [get_pins $pinName] if {$pin == {}} { puts " Error - pin $pinName does not exist" return {} } set direction [get_property DIRECTION $pin] return $direction } 8.2 处理tcl错误

一些内置tcl命令出错时,如果没有对错误进行处理,可能会中断命令执行。例如当执行file命令时发现文件无法打开。 为了处理命令出错的情况,tcl内置了catch命令,当发现错误时返回1,否则返回0。

# catch语法 catch script [varname] # script为一个或一个命令集,varname为报错内容存储变量名。示例如下: If {[catch script errorstring]} { # A low-level TCL_ERROR happened puts " Error - $errorstring " } else { # No TCL_ERROR was generated puts " The code completed successfully " } # 当使用file命令时发现文件无法开始时,通过cathc处理 if {[file exists $filename]} { if {[catch { set FH [open $filename r] } errorstring]} { puts " File $filename could not be opened : $errorstring" } else { # The file is opened, do something # … close $FH } } else { puts " File $filename does not exist" }

error命令可用于产生TCL_ERROR。(这里没看懂,error是用于增加报错内容的吗?)

proc get_file_content { filename } { if {[catch { set FH [open $filename r] set content [read $FH] close $FH } errorstring]} { error " File $filename could not be opened : $errorstring " } return $content } 9.调用外部程序

使用exec调用外部程序,使用时要确保外部程序可以适配当前操作系统。

set result [exec /bin/perl /my_perl_script.pl] 10.自定义设计规则检查(DRC)

DRC定义了一系列设计规则,并检查当前设计是否符合这些规则,打印找到的错误和冲突部分。 可以通过report_drc执行DRC,检查并报告当前设计中的违例部分。 当发现设计违背设计规则时,可以通过create_dec_violation定义且标识违例行为。 可以通过create_dec_check命令创建用户自定义DRC。 可以通过create_dec_ruledeck命令创建drc集合,集合中可以同时有自定义DRC和官方DRC,可以通过add_drc_checks在集合中新增DRC。

10.1 创建tcl检查函数

创建一个proc对设计中感兴趣的对象进行检查,最终以DRC违例报告形式返回一个明确定义的错误。新增的脚本必须在report_drc执行之前导入。

# 示例中dataWidthCheck检查WRITE_B总线宽度是否符合要求。 # This is a simplistic check -- report BRAM cells with WRITE_WIDTH_B wider than 36. proc dataWidthCheck {} { # list to hold violations set vios {} # iterate through the objects to be checked foreach bram [get_cells -hier -filter {PRIMITIVE_SUBGROUP == bram}] { set bwidth [get_property WRITE_WIDTH_B $bram] if { $bwidth > 36} { # define the message to report when violations are found set msg "On cell %ELG, WRITE_WIDTH_B is $bwidth" set vio [ create_drc_violation -name {RAMW-1} -msg $msg $bram ] lappend vios $vio }; # End IF }; # End FOR if {[llength $vios] > 0} { return -code error $vios } else { return {} }; # End IF } ; # End PROC 10.2 新建DRC检查

建立tcl检查函数之后,还需要在vivado中将其定义为DRC,并加入到系统DRC报告内容中。 首先需要通过create_drc_check声明建立新的检查规则,该命令需要用户自定义一个检查名字(向DRC集合中添加该检查,或在DRC报告中找到该检查,会用到名字),在上面的示例中,DRC名为RAMW-1。除此以外,create_drc_check还需要添加proc名字,作为-rule_body,上述例子中,proc名为dataWidthCheck。 DRC检查规则有is_enable特性,可以通过set_property进行配置,当其为fulse时,执行report_drc时不会检查这一条。

10.3 新建DRC检查集

可以手动将多个DRC检查加入DRC检查集,通过create_drc_ruledesk创建检查集,通过add_drc_checks新增检查,通过remove_drc_checks删除检查。

create_drc_ruledeck myrules add_drc_checks -ruledeck myrules {RAMW-1 RAMW-2 RAMW-3} remove_drc_checks {RAMW-2} -ruledeck myrules

DRC检查集也有is_enable属性。

10.4 单独运行自定义DRC

用户自定义的DRC、DRC集合可以单独运行。

report_drc -check {RAMW-1} report_drc -check {RAMW-1 RAMW-2} report_drc -ruledecks myrules 10.5 DRC属性设置

DRC就像其他对象一样可以通过get_property查看属性、set_property设置属性。

Vivado% report_property [get_drc_checks RAMW-1] Property Type Read-only Visible Value ARCHITECTURES string* true true CLASS string true true drc_check DESCRIPTION string true true Block RAM Data Width Check GROUP string true true RAMW HIERNAME string true true RAMB Checks IS_ENABLED bool false true 1 IS_USER_DEFINED bool true true 1 MESSAGE string true true MSG_ID int true true 1 NAME string true true RAMW-1 SEVERITY enum false true Advisory

DRC属性只有IS_ENABLE和SEVERITY可以设置。

set_property IS_ENABLED false [get_drc_checks RAMW-1] set_property SEVERITY {Critical Warning} [get_drc_checks RAMW-1] # 将设置属性初始化 reset_drc_check [get_drc_checks] 11.自定义GUI按钮

Tools > Custom Commands > Customize Commands.

12.提高tcl运行效率的方法

以下几种方法可以提高tcl脚本的运行效率。

12.1运用嵌套

一条命令在tcl控制台执行时,首先通过tcl编译器对命令进行解析,之后进入C++层继续执行这条命令,执行结束后返回正确的值给tcl编译器,之后执行下一条命令,tcl编译器到C++层会消耗一定时间,因此如果可以将两个命令通过嵌套变为一个命令,则系统只需在tcl编译器和C++层变化一次,节省了一定时间。举例来说:

set nets [get_nets -hier] set pins [get_pins -of_objects $nets

这样执行时间将会长于

set pins [get_pins -of_objects [get_nets -hier]] 12.2 缓存对象

当有一些运行结果需要多次使用时,最好先将其设置为一个变量,避免每次使用时都要重新检索或计算一遍。

12.3 vivado的object可以直接当作string作为其他命令的参数

当一些命令需要输入字符串时,vivado的设计对象可以直接输入,而无需再调用设计对象的NAME属性。

if {[regexp {.*enable.*} $MyObject]} { ... } if {[regexp {.*enable.*} [get_property NAME $MyObject]]} { ... }

第一种写法不仅比第二种易读,同时运行时间更短。

12.4 写更高效的代码

提高运行时间的一种方法是高效地编写代码,以便构建一个容器,并在整个容器上运行一个命令,而不是在一个循环中对作为容器一部分的每个项目运行命令。(即将循环中的数据整合成一个列表,之后对列表进行统一操作)。

# 低效写法 foreach bram [get_cells -hier -filter {PRIMITIVE_SUBGROUP == bram}] { set bwidth [get_property WRITE_WIDTH_B $bram] if { $bwidth > 36} { highlight_object -color red [get_cells $bram] }; # End IF }; # End FOR # 高效写法 foreach bram [get_cells -hier -filter {PRIMITIVE_SUBGROUP == bram}] { set bwidth [get_property WRITE_WIDTH_B $bram] if { $bwidth > 36} { lappend bram_list $bram }; # End IF }; # End FOR highlight_object -color red [get_cells $bram_list] 12.5 获取用户输入

在通常的tclsh环境下,通过stdin获取用户输入:

gets stdin answer

而在vivado环境下,tcl脚本会在不同系统环境下运行,stdin很难兼容所有情况。因此除非确定脚本只在某些情况下运行,并且stdin可以全部兼容时,才使用这个命令。 vivado环境下,通常建议将所有输入整合成一个tcl脚本文件,之后通过source来执行输入文件,即可完成参数输入。用户需要更改配置时,只需要更改文件中的数据即可。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3