手把手教你用Python实现Vivado和ModelSim仿真自动化

本文转载自:网络交换FPGA微信公众号

芯片设计从RTL代码一直到最后流片的GDSII文件,都是文本文件,因此,掌握文本分析处理语言是集成电路设计的一项重要的基本功。本公众号一直致力于推广采用文本分析工具来提升仿真和综合效率的方法。详见本公众号专辑《芯片设计课程及相关实验》。本文是孙义雯同学采用Python语言实现的Vivado和Modelsim联合仿真的自动化脚本,已经在实际项目中经过较长时间的检验,今天开源出来,供大家学习,欢迎留言交流心得体会和改进建议。同时,后续我们会陆续推出基于Python的Verilog代码智能化分析处理文章,敬请期待。

我们在Windows系统下使用Vivado的默认设置调用第三方仿真器比如ModelSim进行仿真时,一开始仿真软件都会默认在波形界面中加载testbench顶层的信号波形,并自行仿真1000ns后停止。当我们想查看对应模块的波形时,需要自己去手动添加,并且为了防止跑一段时间仿真后,添加新模块或者信号却发现没有记录波形,就要提前手动在控制台上执行log -r ./*命令来实现对全部信号波形的记录。但是每当我们修改完代码,关闭重启仿真器再一次仿真时,就需要将之前的操作(删改添加对应模块信号,执行log -r ./*等)重新完成一遍才能继续跑出想看的信号波形。尽管可以通过将仿真时添加的模块信号保存为*.do文件,下次仿真通过执行do *.do的形式来快速添加之前波形;但在频繁修改代码,需要经常重新仿真的情况下,每次都手动去添加信号的操作会比较影响到我们的情绪,那么能否通过脚本语言比如Python来实现一键仿真并自动添加好所需要的模块信号呢?
首先我们需要对Vivado软件实现第三方仿真的过程进行分析,弄清楚其中的步骤。

1. Vivado软件第三方仿真分析

1.1 Vivado调用第三方软件方式

参考之前的文章:用Modelsim独立仿真带Vivado IP核的仿真工程,
https://mp.weixin.qq.com/s/6Q4H9PsHt60_xC1VXkBDOw ,我们可以知道Vivado软件执行和生成仿真文件是在工程的仿真目录工程名.sim/下,然后我们打开一个示例工程的仿真目录,如图所示

结合图片和文章我们可以看出,Vivado软件调用第三方仿真器的方式,是根据之前在工程文件中设置的仿真器路径和联调库路径等参数,在工程仿真目录下生成一系列仿真用的Tcl脚本和系统的批处理脚本(Linux下就是shell),通过系统命令执行上面的批处理脚本(先compile,再elaborate,最后simulate),来调用第三方仿真软件实现仿真功能。

1.2 Vivado仿真脚本分析

上面图片中共有三个批处理脚本:compile.bat、elaborate.bat、simulate.bat。依次分别实现了三种功能:compile、elaborate、simulate。下面我们对每个批处理脚本的内容分别进行分析。

1.2.1 compile相关脚本

compile.bat脚本全部内容如下:

@echo off
set bin_path=C:\questasim64_10.6c\win64
call %bin_path%/vsim -c -do "do {xxxxxxxx_compile.do}" -l compile.log
if "%errorlevel%"=="1" goto END
if "%errorlevel%"=="0" gotoSUCCESS
:END
exit 1
:SUCCESS
exit 0

本工程设置的第三方仿真器为Questasim,脚本中具体功能实现在第2和第3行:
1. 设置二进制文件路径bin_path;
2. 调用该路径下的vsim程序执行do {xxxxxxxx_compile.do}命令。

其中的xxxxxxxx_compile.do是跟批处理脚本同一目录下的Tcl脚本文件,文件内容如下(已添加注释):

######################################################################
#
# File name : xxxxxxxx_compile.do
# Created on: Fri Aug 14 16:40:09 +0800 2020
#
# Auto generated by Vivado for 'behavioral' simulation
#
######################################################################
#新建work库
vlib work
#新建msim库
vlib msim

#新建msim/xil_defaultlib库
vlib msim/xil_defaultlib

#将目前的逻辑工作库xil_defaultlib和实际工作库msim/xil_defaultlib映射对应
vmap xil_defaultlib msim/xil_defaultlib

#编译verilog代码文件(VHDL文件需要用vcom命令),编译到xil_defaultlib库下
vlog -64 -incr -work xil_defaultlib  \
#vivado的官方IP核提供的仿真代码文件
"../../../xxxxxxxxxxxx.srcs/sources_1/ip/xxxxxxxxxxxx/xxxxxxxxxxxx_sim_netlist.v" \
"../../../xxxxxxxxxxxx.srcs/sources_1/ip/xxxxxxxxxxxx/xxxxxxxxxxxx_sim_netlist.v" \
"../../../xxxxxxxxxxxx.srcs/sources_1/ip/xxxxxxxxxxxx/xxxxxxxxxxxx_sim_netlist.v" \
"../../../xxxxxxxxxxxx.srcs/sources_1/ip/xxxxxxxxxxxx/xxxxxxxxxxxx_sim_netlist.v" \
"../../../xxxxxxxxxxxx.srcs/sources_1/ip/xxxxxxxxxxxx/xxxxxxxxxxxx_sim_netlist.v" \
#工程中自行编写的代码文件
"../../../../rtl/xxx/xxxxxxx.v" \
"../../../../rtl/xxxx/xxxxxxx.v" \
以下省略若干行的代码文件........


# compile glbl module
vlog -work xil_defaultlib "glbl.v"

#强制退出
quit -force

看过上述代码和注释,我们可以了解到compile脚本通过调用第三方仿真器执行对应的Tcl脚本xxxxxxxx_compile.do实现了对工程中代码文件的编译,其中xxxxxxxx_compile.do中会写入全部工程代码文件的路径。

1.2.2 elaborate脚本

elaborate.bat脚本全部内容如下:

@echo off
set bin_path=C:\questasim64_10.6c\win64
call %bin_path%/vsim  -c -do "do {xxxxxxxx_elaborate.do}" -l elaborate.log
if "%errorlevel%"=="1" goto END
if "%errorlevel%"=="0" goto SUCCESS
:END
exit 1
:SUCCESS
exit 0

同样可以看出,该脚本中实现的具体功能为:
1. 设置二进制文件路径bin_path;
2. 调用该路径下的vsim程序执行do {xxxxxxxx_elaborate.do}命令。

其中xxxxxxxx_elaborate.do脚本的内容如下(已加注释):

######################################################################
#
# File name : xxxxxxxx_elaborate.do
# Created on: Fri Aug 14 16:40:15 +0800 2020
#
# Auto generated by Vivado for 'behavioral' simulation
#
######################################################################
#vopt是在使用vcom或vlog编译设计后对其执行全局优化
vopt -64 +acc=npr -L xil_defaultlib -L unisims_ver -L unimacro_ver -L secureip -L xpm -work xil_defaultlib xil_defaultlib.xxxxxxxxx xil_defaultlib.glbl -o xxxxxxxxx_opt
#强制退出
quit -force

可以看出上述脚本主要实现的功能是对代码编译后的设计文件进行全局优化。

1.2.3 simulate脚本

simulate.bat脚本全部内容如下:

@echo off
set bin_path=C:\questasim64_10.6c\win64
call %bin_path%/vsim   -do "do {xxxxxxxx_simulate.do}" -l simulate.log
if "%errorlevel%"=="1" goto END
if "%errorlevel%"=="0" goto SUCCESS
:END
exit 1
:SUCCESS
exit 0

该脚本中实现的具体功能为:
1. 设置二进制文件路径bin_path;
2. 调用该路径下的vsim程序执行do {xxxxxxxx_simulate.do}命令。

其中xxxxxxxx_simulate.do脚本的内容如下(已加注释):

######################################################################
#
# File name : xxxxxxxx_simulate.do
# Created on: Fri Aug 14 16:40:22 +0800 2020
#
# Auto generated by Vivado for 'behavioral' simulation
#
######################################################################
#指定vsim查找设计单元的默认工作库为xil_defaultlib,并对指定的优化后的设计开始仿真
vsim -lib xil_defaultlib xxxxxxxx_opt

#设置变量NumericStdNoWarnings为1,即禁用在加速的numeric_std和numeric_bit包中生成的警告
set NumericStdNoWarnings 1
#设置变量StdArithNoWarnings为1,即禁用在加速Synopsys标准软件包中生成的警告
set StdArithNoWarnings 1

#执行xxxxxxxx_wave.do文件中的tcl指令
do {xxxxxxxx_wave.do}

#打开wave窗口
view wave
#打开structure窗口
view structure
#打开signals窗口
view signals

#执行xxxxxxxx.udo文件中的tcl指令
do {xxxxxxxx.udo}

#仿真1000ns
run 1000ns

该脚本中实现了调用第三方仿真软件对优化后的设计开始仿真,调出仿真界面,执行xxxxxxxx_wave.do脚本,打开仿真相关窗口,并继续执行xxxxxxxx.udo脚本。
下面我们接着给出xxxxxxxx_wave.do脚本的内容:

######################################################################
#
# File name : `xxxxxxxx_wave.do
# Created on: Fri Aug 14 16:40:22 +0800 2020
#
# Auto generated by Vivado for 'behavioral' simulation
#
######################################################################
#添加当前模块内(即仿真顶层模块)信号
add wave *
#添加GSR(全局复置位信号)
add wave /glbl/GSR

可知xxxxxxxx_wave.do脚本文件主要功能是实现对仿真波形的添加。而另一个xxxxxxxx.udo脚本中无实际内容:

######################################################################
#
# File name : xxxxxxxx.udo
# Created on: Wed Jun 10 16:36:49 +0800 2020
#
# Auto generated by Vivado for 'behavioral' simulation
#
######################################################################

说明在默认设置下,Vivado是不通过xxxxxxxx.udo脚本执行命令的。

1.2.4 仿真脚本总结

至此,我们基本弄清楚了这三个脚本的功能以及与其他脚本文件之间的关系:
1. compile.bat脚本主要实现对仿真代码文件的编译;
2. elaborate.bat脚本主要对编译后的设计进行全局的优化,并生成优化后的结果;
3. simulate.bat脚本则是具体调出仿真软件界面对优化后的设计进行仿真,控制仿真软件相关窗口的显示、模块信号的添加等等其他与最终波形仿真相关的操作。

关窗口的显示、模块信号的添加等等其他与最终波形仿真相关的操作。
于是,如果想通过python脚本实现仿真自动化添加波形等的操作,那么就需要在simulate.bat脚本及其相关的do脚本文件上做文章。

1.3 Vivado仿真功能选项

在进一步分析Vivado仿真操作,思考如何使用python脚本实现我们想要的自动化之前,我们可以先问这样一个问题:
难道Vivado软件真的没有提供仿真自动添加自定义波形等等方便仿真操作的功能吗?
关于这个问题,我们可以先去查看Vivado软件仿真功能自带的仿真选项,就在设置第三方仿真器路径和联调库路径的地方,如图所示:

可以看到在下面红框中有三个标签页Compilation、Elaboration、Simulation,分别对应仿真时的三个步骤和生成的相关脚本文件。

打开Simulation标签页,我们可以看到下面有多行选项,其中就有runtime仿真时间,默认是1000ns。然后下一行红横线上内容是log_all_signals,“记录全部信号”,那这个是仿真自动执行log -r ./*命令的选项吗?我们先将该选项打勾。

然后接着向下看,有个custom_wave_do的选项,在上面脚本分析中我们知道xxxxxxxx_wave.do脚本文件实现的是仿真添加模块信号的功能,那这里是仿真添加自定义信号的选项吗?

这里我将之前该工程仿真保存的一版信号文件wave.do拷贝到仿真脚本所在behave目录的上一级目录下:

然后如上面仿真选项图中所示写入custom_wave_do选项的路径为:../wave.do,然后重新开始仿真。

出现仿真波形界面如图:

该界面中添加的信号分组与../wave.do文件中设置的信号相同,说明这里实现了对自定义信号的自动添加。接着我们再打开仿真目录下的xxxxxxxx_simulate.do脚本查看,其内容如下:

######################################################################
#
# File name : xxxxxxxx_simulate.do
# Created on: Fri Aug 14 21:15:40 +0800 2020
#
# Auto generated by Vivado for 'behavioral' simulation
#
######################################################################
vsim -lib xil_defaultlib xxxxxxxx_opt

set NumericStdNoWarnings 1
set StdArithNoWarnings 1

do {../wave.do}

view wave
view structure
view signals

log -r /*

do {xxxxxxxx.udo}

run 1000ns

可以看到在Vivado生成的该脚本中第14行和第20行,已经自动添加上了do {../wave.do}和log -r /*的命令,说明的确在仿真时就可以实现自动记录全部信号波形和自动添加自定义信号的操作。

因此,Vivado本身就已经提供了这样方便的仿真操作选项,如果只是想实现自动记录全部信号波形和自动添加保存的自定义信号波形操作的话,我们直接设置修改仿真选项中的log_all_signals和custom_wave_do两个选项的内容即可。

但是初出茅庐的我并没有想到向Vivado提出问题,而是按照自己的想法开始了Python脚本的折腾之路。

2. 初步设想功能的Python实现

在1.2节对仿真脚本的分析中,我们知道了如果想实现仿真自动添加和记录信号的功能,就需要对仿真最后一步simulate.bat相关的脚本进行对应的修改。但首先,这些脚本都是Vivado仿真时自动生成的,那如果一开始没有脚本,或者我们添加了一些代码文件后,脚本需要更新怎么办?(compile.bat相关的.do脚本中没有新加入的代码文件的路径,自然就无法去编译新的代码)因此我们就需要首先让Vivado来产生仿真相关的最新脚本,再去实现对脚本的更改。

2.1 Vivado软件仿真脚本生成

参考实验室张仲禹同学的这篇文章

实验室自研工具Vivado Batch Mode Tool介绍!

https://mp.weixin.qq.com/s/EcrZl8iM0SZbpLrlXUJ6CA ,我们可以了解到Vivado软件在运行上提供了批处理模式batch mode,通过批处理模式和Tcl脚本可以让Vivado软件对特定工程执行具体功能。因此这里只要利用批处理模式让Vivado软件对当前工程生成仿真脚本即可。那么如何去查找生成仿真脚本的对应指令呢?当我们点击Vivado工程GUI界面的仿真按钮时,界面底部的控制台Tcl Console便会立刻显示并执行launch_simulation的命令,如图所示:

我们打开Vivado的Tcl命令手册UG835,查找到launch_simulation命令词条如下:

launch_simulation命令是运行仿真的命令,可以看到语法中有一个-scripts_only选项,其描述是:仅生成脚本。这里的脚本自然是相关的仿真脚本。只要利用这个命令选项就能实现对工程仿真脚本的生成。当然,我们想实现的是功能仿真,因此在执行该命令时,最好在上面的-mode选项中选择behavioral。

那么,实现对功能仿真脚本生成功能的命令就应该是:launch_simulation -mode behavioral -scripts_only,为了验证命令正确性,我们可以利用Vivado工程的GUI界面提前进行测试,在删除仿真目录下的文件后,在命令控制台Tcl Console输入并执行该命令,执行完后检测仿真目录下是否生成了相关脚本,经过验证,该命令可以生成仿真脚本。

2.2 功能实现思路

至此,我们针对功能的实现思路做如下总结:
1. 使用Python调用Vivado软件的批处理模式batch mode执行launch_simulation -mode behavioral -scripts_only命令,来生成当前工程的功能仿真脚本;
2. 利用python可以对生成的simulate.bat相关的脚本内容进行修改,添加log -r ./*以实现自动记录全部信号波形,添加执行自定义wave.do脚本命令或将想添加的信号文件内容替换进默认生成的xxxxxxxx_wave.do脚本中,完成仿真时对自定义信号的自动化添加;
3. 使用python中的系统函数库依次执行compile.bat、elaborate.bat、simulate.bat脚本,调出仿真界面执行仿真。

2.3 具体实现

2.3.1 使用Python调用Vivado软件生成仿真脚本

具体实现代码和注释如下:

import os

# 指定工程xpr文件路径
XprFilePath = 'xxxxxxxx_project.xpr'
# 指定Tcl脚本路径
SimTclFilePath = 'sim.tcl'

# 命令-在使用命令行调用Vivado软件前需要运行的批处理文件
SourceSettingsFileCmd = 'call C:/Xilinx/Vivado/2017.2/settings64.bat' 
# 命令-调用Vivado软件的batch mode打开当前工程文件并执行Tcl脚本中的生成仿真脚本命令
VivadoBatchModeScriptsCmd = 'vivado -mode batch -source ' + SimTclFilePath + ' -nojournal -nolog ' + XprFilePath

# 组合前两条命令并调用系统函数依次执行
os.system(SourceSettingsFileCmd + ' && ' + VivadoBatchModeScriptsCmd)

其中上面代码中用到的sim.tcl文件内容为:

#仅生成仿真脚本
launch_simulation -mode behavioral -scripts_only

将两个脚本放置于工程xpr文件的同目录下,使用Python3执行,执行后仿真目录结果如下:

接下来我们依次对生成的脚本进行检查,查看本次使用Vivado的batch mode生成的仿真脚本与第一章中脚本内容是否存在差别。

经过对比,发现除了xxxxxxxx_simulate.do文件和simulate.bat脚本存在一点差别外,其他脚本内容完全相同,存在差别的脚本内容如下
xxxxxxxx_simulate.do内容:

######################################################################
#
# File name : xxxxxxxx_simulate.do
# Created on: Sat Aug 15 15:59:41 +0800 2020
#
# Auto generated by Vivado for 'behavioral' simulation
#
######################################################################
vsim -lib xil_defaultlib xxxxxxxx_opt

set NumericStdNoWarnings 1
set StdArithNoWarnings 1

do {xxxxxxxx_wave.do}

view wave
view structure
view signals

do {xxxxxxxx.udo}

run 1000ns

quit -force

内容差别在最后一行,相比Vivado的GUI界面下点击仿真按钮生成的脚本,batch mode下使用仿真选项-scripts_only生成脚本xxxxxxxx_simulate.do的最后一行额外添加了强制退出的命令quit -force,这会导致调出的仿真程序在执行完全部的仿真命令后,立刻关闭界面并退出程序,所以仿真时,我们需要在该脚本执行前删除掉该命令。

@echo off
set bin_path=C:\questasim64_10.6c\win64
call %bin_path%/vsim  -c -do "do {xxxxxxxx_simulate.do}" -l simulate.log
if "%errorlevel%"=="1" goto END
if "%errorlevel%"=="0" goto SUCCESS
:END
exit 1
:SUCCESS
exit 0

内容差别在第三行,在vsim命令后多出了一个-c的选项,查找QuestaSim命令手册中vsim词条的-c选项描述如图:

可以看出-c选项是以命令行模式command-line mode执行vsim命令,也就是说,该选项执行后打开的不是仿真软件的GUI界面,而是黑乎乎的命令行界面,这将导致我们无法实时查看跑出的仿真波形,所以在仿真时,-c也需要删除掉。

内容差别总结
因此,在后续对脚本的处理中,我们除了添加log -r ./*命令和将想添加的信号替换进xxxxxxxx_wave.do脚本中之外,还需要额外删除脚本xxxxxxxx_simulate.do最后一行的quit -force,以及脚本simulate.bat中vsim命令后的-c。

2.3.2 使用Python修改并执行仿真脚本

具体实现代码和注释如下:
simulate.bat内容:

import os

# 仿真目录路径
SimDirPath = 'xxxxxxxx.sim/sim_1/behav/'
# compile批处理脚本名称
CompileBatName = 'compile.bat'
# elaborate批处理脚本名称
ElaborateBatName = 'elaborate.bat'
# simulate批处理脚本名称
SimulateBatName = 'simulate.bat'
# 由于所执行的脚本内容里存在一些相对路径,所以在执行脚本前,需要将系统路径切换到所执行脚本所在的目录下
# 执行Compile脚本
os.system('cd ' + SimDirPath + ' && ' + 'call ' + CompileBatName)
# 执行Elaborate脚本
os.system('cd ' + SimDirPath + ' && ' + 'call ' + ElaborateBatName)

# 修改xxxxxxxx_simulate.do脚本,删除run 1000ns和quit -force,添加log -r ./*
SimulateDoFile = open(SimDirPath + 'xxxxxxxx_simulate.do', 'r')
SimulateDoFileAllLines = SimulateDoFile.readlines()
SimulateDoFile.close()
SimulateDoFile = open(SimDirPath + 'xxxxxxxx_simulate.do', 'w')
for EachLine in SimulateDoFileAllLines:
    if EachLine.find('run 1000ns') == -1 and EachLine.find('quit -force') == -1:
        SimulateDoFile.writelines(EachLine)
SimulateDoFile.writelines('\nlog -r ./*\n')
SimulateDoFile.close()

# 删除simulate.bat脚本中的-c选项内容
SimulateBatFile = open(SimDirPath + SimulateBatName, 'r')
SimulateBatFileAllLines = SimulateBatFile.readlines()
SimulateBatFile.close()
SimulateBatFile = open(SimDirPath + SimulateBatName, 'w')
for EachLine in SimulateBatFileAllLines:
    if EachLine.find('%bin_path%/vsim  -c -do') != -1:
        EachLine = EachLine.replace('%bin_path%/vsim  -c -do', '%bin_path%/vsim  -do')
    SimulateBatFile.writelines(EachLine)
SimulateBatFile.close()

# 将当前目录下信号文件wave.do中的内容覆写到仿真目录下的xxxxxxxx_wave.do文件中
SimWaveDoFile = open('wave.do', 'r')
SimWaveDoFileAllLines = SimWaveDoFile.readlines()
SimWaveDoFile.close()
SimWaveDoFile = open(SimDirPath + 'xxxxxxxx_wave.do', 'w')
SimWaveDoFile.writelines(SimWaveDoFileAllLines)
SimWaveDoFile.close()

# 执行Simulate脚本
os.system('cd ' + SimDirPath + ' && ' + 'call ' + SimulateBatName)

上述代码中涉及到的信号文件wave.do依旧是之前第1.3节测试仿真选项时使用的文件,将其放置在脚本所在的目录下,然后使用Python3运行脚本,运行结果截图如下:

可以看到成功实现了对仿真软件的调用以及波形的自动添加,而且在使用时也不需要打开Vivado工程的GUI界面。

2.4 实现总结与展望
上述代码功能的具体实现主要分以下几个步骤:
1. 使用Python系统函数调用Vivado的batch mode对当前工程执行sim.tcl脚本中的launch_simulation -mode behavioral -scripts_only命令,生成功能仿真的相关脚本;
2. 使用Python系统函数在仿真目录下依次执行生成的compile.bat和elaborate.bat脚本,实现对仿真代码文件的编译和后续的全局优化;
3. 读取生成的xxxxxxxx_simulate.do脚本文件内容,删除其中的run 1000ns和quit -force,并添加上log -r ./*后重新覆写xxxxxxxx_simulate.do脚本;
4. 读取生成的simulate.bat脚本文件内容,删除其中vsim命令后的-c选项后,重新覆写simulate.bat脚本;
5. 读取Python脚本所在目录下wave.do文件的内容,覆写到仿真目录下xxxxxxxx_wave.do脚本文件中,实现在仿真时对自定义信号的添加;
6. 使用Python的系统函数在仿真目录下执行simulate.bat脚本,调出仿真程序界面。

至此,我们就用总计约60行不到的Python代码外加1行的Tcl代码实现了最初设想的目标。但是,这样的脚本还存在着许多粗糙的缺陷或有待发展之处:

  • 代码各种变量如工程文件路径、软件版本路径、仿真目录路径等的赋值都是固定赋值,那么在将脚本迁移到另一个工程下使用时,就需要根据版本和工程目录名等环境自行去修改脚本,无法做到傻瓜式操作;
  • 代码从调用Vivado的batch mode生成仿真脚本,再到compile、elaborate,最后到simulate,总共的耗时较长,对于简单的修改代码后进行仿真,不涉及到代码文件添加或IP核添加时,是不需要重新生成仿真脚本的,直接从compile开始就行;而对于无代码修改的情况,直接执行simulate脚本进行最后一步即可,就更不需要前面这些花费时间较长的步骤;
  • 除去仿真功能外,我们平时进行Vivado开发时,还存在许多本可以用脚本自动化实现的繁琐操作,这些均可以整合进一个或一组脚本中。
  • 3. 优化功能的Python实现

    第二章最后总结给出了初始脚本的缺陷和有待发展之处,其中第二项缺陷可以通过提供多种条件选项的方式,实现多种仿真步骤,解决较为简单。本章将主要针对第二章总结的第一项缺陷进行分析,并加以解决。

    3.1 优化功能分析

    我们期望将脚本拷贝到另一版工程时,可以不经修改直接运行,这就要求脚本可以自动提取到其运行所需的一系列参数,从而直接对环境进行匹配。首先让我们总结一下初步功能的Python实现中的具体缺陷,有哪些地方在迁移到另一工程后需要修改。

    3.1.1 参数分析

    这里以流水账的形式,对初步功能的Python代码中用到的全部参数进行罗列和分析:

    3.1.1.1 指定的工程xpr文件路径

    # 指定工程xpr文件路径
    XprFilePath = 'xxxxxxxx.xpr'

    该参数的作用是能够让Vivado软件针对当前指定的工程实现具体功能,每当脚本拷贝到另一个不同的工程时,该参数基本肯定会改变,所以最好能进行自动识别。每个工程在其工程目录下默认都只有一个xpr文件,而Python也提供了返回指定系统目录下文件名列表的函数os.listdir,可以很方便的实现对特定文件名称的获取,只需对文件名进行判别是否是xpr文件即可实现自动识别。

    3.1.1.2 指定的Tcl脚本路径

    # 指定Tcl脚本路径
    SimTclFilePath = 'sim.tcl'

    由于指定Tcl脚本是跟随Python脚本一起的,与实际仿真哪个工程无关,名称和位置都固定,因此这里可以继续使用固定赋值。

    3.1.1.3 执行的系统命令

    # 命令-在使用命令行调用Vivado软件前需要运行的批处理文件
    SourceSettingsFileCmd = 'call C:/Xilinx/Vivado/2017.2/settings64.bat' 
    # 命令-调用Vivado软件的batch mode打开当前工程文件并执行Tcl脚本中的生成仿真脚本命令
    VivadoBatchModeScriptsCmd = 'vivado -mode batch -source ' + SimTclFilePath + ' -nojournal -nolog ' + XprFilePath

    可以将执行的系统命令中新用到的路径分解为以下几部分:
    1. 系统执行脚本命令:call
    2. Xilinx软件的安装目录:C:/
    3. Vivado工程的版本:2017.2
    4. 需要调用的settings脚本名称:settings64.bat

    第二个生成仿真脚本命令VivadoBatchModeScriptsCmd涉及到的Tcl脚本路径和工程xpr文件路径已经说过。
    此处暂时只考虑Windows系统实现,因此上述分解部分中可以考虑自动识别的是:安装目录C:/和工程版本2017.2
    其中安装目录在同一个系统中都是固定的,不需要频繁修改,所以此处仅需要读取xpr工程文件内容,实现版本的自动识别。

    # 仿真目录路径
    SimDirPath = 'xxxxxxxx.sim/sim_1/behav/'

    对上述路径进行分解得:
    1. 工程名:xxxxxxxx
    2. 仿真集名称:sim_1
    3. 仿真脚本所在目录(2017.2版本):behav

    同样由于工程名与xpr工程文件名默认相同,并且在工程目录下默认只有一个后缀名为.sim的目录,因此同样可以很容易的实现对该目录名的识别;对于仿真集名称,由于同一版工程中可以设置多个不同的仿真集,但在xpr文件内容中对当前有效的仿真集有专门的参数表示,因此可以通过解析xpr文件内容的形式识别出当前仿真集名称;

    值得注意的是:不同版本的VIvado在仿真子目录的路径上存在一些差别,例如2017.2的仿真脚本全部生成在behav目录下,但2018.2版本则在behav目录下还有针对每个仿真器的子目录,目录名为小写的仿真器名称,所以我们还需要读取xpr文件内容,识别出当前设置的目标仿真器,才能最终实现对仿真目录的正确组合。

    3.1.1.5 批处理脚本名称
    3.1.1.4 仿真目录路径(Vivado 2017.2)

    # compile批处理脚本名称
    CompileBatName = 'compile.bat'
    # elaborate批处理脚本名称
    ElaborateBatName = 'elaborate.bat'
    # simulate批处理脚本名称
    SimulateBatName = 'simulate.bat'

    在同一类型的系统下,Vivado软件默认生成的仿真相关批处理脚本的名称均为固定,因此此处无需修改。

    3.1.1.6 总结

    在初步功能代码实现的基础上,我们至少要对如下参数实现自动识别:
    1. 工程xpr文件路径(主要是工程名)
    2. 工程文件版本
    3. 工程有效仿真集名称
    4. 工程当前仿真器名称

    3.1.2 xpr文件解析

    3.1.2.1 xpr文件结构介绍
    xpr文件是包含了Vivado工程核心配置内容的文本文件,采用的是xml的内容格式,下面给出一个xpr文件结构示例:

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Product Version: Vivado v2017.2 (64-bit)              -->
    <!--                                                         -->
    <!-- Copyright 1986-2017 Xilinx, Inc. All Rights Reserved.   -->
    
    <Project .............>
     <Configuration>
          .............
      </Configuration>
        
      <FileSets>
          .............
      </FileSets>
        
    <Simulators>
          .............
     </Simulators>
        
      <Runs >
          .............
      </Runs>
    </Project>

    可以看到整个xpr文件内容基本全部包含在一个根节点Project内,根节点内部又包含四个子节点Configuration、FileSets、Simulators、Runs。经过对实际的工程xpr文件内容进行分析,我们可以了解到四个子节点的主要内容为:
    1. Configuration:Vivado工程的一系列配置参数,例如:FPGA芯片型号、仿真联调库路径、目标仿真器等等;
    2. FileSets:添加进该工程的全部代码、IP核或约束文件路径,及一系列相关的文件属性:设定的顶层模块名称、代码是否用于综合、实现、仿真等;
    3. Simulators:包含对当前类型系统下能够使用的仿真器的描述等;
    4. Runs:包含需要进行Synthesis、Implementation的IP核文件、代码设计的一系列参数配置。

    下面我们结合上一节优化分析的结论,对所需要的xpr文件内容进行进一步的分析和查找。需要解析xpr得到的参数有:工程版本、有效仿真集名称、当前仿真器名称。

    3.1.2.2 工程版本

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Product Version: Vivado v2017.2 (64-bit)              -->
    <!--                                                         -->
    <!-- Copyright 1986-2017 Xilinx, Inc. All Rights Reserved.   -->

    经过一番查找,在根节点Project中并没有发现跟Vivado版本相关的参数,唯一与版本相关的内容仅出现在内容第二行:

    因此我们只能将该行Vivado v后的内容2017.2提取为版本号使用。

    3.1.2.3 有效仿真集名称

    表示当前工程的仿真集的参数名为ActiveSimSet,在子节点Configuration中,示例内容如下:

    <Option Name="ActiveSimSet" Val="sim_1"/>

    3.1.2.4 当前仿真器名称

    内容中表示当前仿真器名称的参数名为TargetSimulator,同样在子节点Configuration中,示例内容如下:

    <Option Name="TargetSimulator" Val="Questa"/>

    在具体针对xml格式文件的解析上,Python提供了一个xml函数库,使用其中的parse函数可以对xml文本进行简单高效的处理。

    3.2 功能实现思路

    根据上述分析,总结优化功能的实现思路如下:
    1. 利用Python的os库函数实现对特定路径下工程文件尤其是xpr文件的查找;
    2. 在查找到xpr工程文件后,读取文件内容获取版本号;
    3. 使用Python的xml库函数对查找到的xpr文件文本内容进行解析,提取出有效仿真集名称和当前仿真器名称;
    4. 根据上述步骤获取到的信息,组合相关参数,继续完成第二章实现的功能。

    3.3 具体实现

    首先利用Python的os库中的相关函数实现对工程文件路径的查找,相关代码和注释如下:

    import os
    
    # 获取并返回对应文件或目录路径名列表
    def getProjFilePathList(Path = './', FilePartName = '.xpr'):
        # 判断传入参数是否为字符串
        if type(Path) != str or type(FilePartName) != str:
            print('Error: The type of parameter is wrong! Please ensure each input parameter is a string!')
            return []
        # 判断传入的路径参数是否存在
        if os.path.isdir(Path) == False:
            print('Error: The path does not exist!')
            return []
        # 查找目标文件,并将查找结果路径记录到FilePathList的列表中
        FilePathList = []
        for FileName in os.listdir(Path):
            if FileName.find(FilePartName) != -1:
                FilePathList.append(Path + FileName)
        # 判断是否查找到对应文件或目录路径
        if len(FilePathList) == 0:
            print('Error: Can not find any file or dir whose name including "' + FilePartName + '" in "' + Path + '"')
        return FilePathList
    
    # 获取并返回对应文件或目录路径名列表中的第一个路径
    def getProjFilePath(Path = './', FilePartName = '.xpr'):
        # 获取并返回对应文件或目录路径名列表
        FilePathList = getProjFilePathList(Path, FilePartName)
        # 返回列表中第一个元素
        if len(FilePathList) == 0:
            return ''
        else:
            return FilePathList[0] 

    然后根据查找到的xpr工程文件路径,读取内容,从中提取版本号:

    # 获取并返回当前工程版本
    def getProjVersion(XprFilePathName):
        VivadoProjVerLine = ''
        VivadoProjVer = ''
        VivadoProjVerLoc = -1
        # 打开工程文件查找版本号所在行
        XprFile = open(XprFilePathName, "r")
        for string in XprFile.readlines():
            if string.find('Product Version: Vivado v') != -1:
                VivadoProjVerLoc = string.find('Product Version: Vivado v') + len('Product Version: Vivado v')
                VivadoProjVerLine = string
                break
        XprFile.close()
        if VivadoProjVerLoc == -1:
            VivadoProjVer = ''
            print('Error: Can not find the version of proj, please ensure the xpr file is ok!')
            return VivadoProjVer
        # 判断查找到的版本号字符串是否合法
        for CharVer in VivadoProjVerLine[VivadoProjVerLoc :]:
            if CharVer == ' ' and VivadoProjVer != '':
                break
            elif CharVer >= '0' and CharVer <= '9':
                VivadoProjVer = VivadoProjVer + CharVer
            elif CharVer == '.':
                VivadoProjVer = VivadoProjVer + CharVer
            else:
                VivadoProjVer = ''
                print('Error: Can not identify the version of this xpr file!')
                return VivadoProjVer
        return VivadoProjVer

    使用Python的xml库函数解析xpr文件,提取出有效仿真集名称ActiveSimSet和当前仿真器名称TargetSimulator:

    from xml.dom.minidom import parse
    
    # 解析xpr工程文件
    doc = parse(XprFilePath)
    root = doc.documentElement
    # 记录解析出的四个节点
    Configuration = root.getElementsByTagName('Configuration')[0]
    FileSets = root.getElementsByTagName('FileSets')[0]
    Simulators = root.getElementsByTagName('Simulators')[0]
    Runs = root.getElementsByTagName('Runs')[0]
    # 创建节点Configuration中Option的关键词字典,初始化为空
    ConfigurationOptionDict = {}
    # 创建并初始化节点Configuration中Option的Name关键词集合
    ConfigurationOptionNameSet = {'Part', 'CompiledLibDir', 'TargetSimulator', 'ActiveSimSet', 'DefaultLib'}
    # 查找xpr工程文件中Configuration节点中的关键词并记录到关键词字典中
    Options = Configuration.getElementsByTagName('Option')
    for Option in Options:
        if Option.hasAttribute('Name') and Option.hasAttribute('Val'):
            for keyword in ConfigurationOptionNameSet:
                if Option.getAttribute('Name') == keyword:
                    ConfigurationOptionDict[keyword] = Option.getAttribute('Val')

    至此我们就完成了对优化功能的实现,与第二章的代码进行整合,就可以自行实现对不同版本不同名称工程的自动识别,并进行仿真。

    4. 后记

    前一、二、三章主要按照时间顺序,详细介绍了使用Python脚本实现对仿真自动化操作功能时,一系列的学习研究过程。对整个过程中所涉及到的关键点都进行了说明与分析。当然,按照上述过程最终编写出的脚本还是较为粗糙,实现的功能也很有限。在后续的学习中,笔者根据使用体验对该脚本做了重构和多次迭代,加入了一些新的功能,同时也拓宽了使用场景,增强了可扩展性。当然受限于笔者的个人水平,目前为止脚本仍然很粗糙,不过已经可以为项目中Vivado工程开发的特殊需求提供一个简单的辅助框架,可以比较自由地添加一些简单功能,笔者打算结合后续项目开发中遇到的问题和使用体验,继续对其进行优化,争取让其能成为一个比较可依赖的好助手。

    最新文章

    最新文章