vivado的vio怎么使用_Vivado功能完善:如何用Tcl/VIO更新BRAM中的数据

Quartus软件工具向来都比较容易上手,尤其是提供了一些辅助工具,用起来非常方便。本文介绍一个Quartus工具支持但是ISE/Vivado不支持的小功能,并给出Vivado的实现方案,让Xilinx FPGA的开发/使用也更加便利。

在使用FPGA做设计的时候,有时候的一些场景,不太适合仿真来进行验证。比如:

1.已经完成了仿真,需要上板测试获取结果和仿真结果做对比,验证设计实现的正确性,自然就不能再用仿真来对比仿真结果;

2.一些接口的仿真,比如外界DAC测试,仿真只能到FPGA的output pin,无法涵盖PCB的走线部分和DAC的处理,也就无法验证DAC是否能正常工作。

这种需求都是要求上板测试的。这种测试工程,通常并不复杂,上板测试也不是非常麻烦。不过有一个需要考虑的问题,就是数据源。

仿真的数据源可以写死在testbench中,但更常用的方法是写在文件中,由testbench读文件来获取数据源。上板实测的问题就是,测试数据源从哪里来。

通常有如下几个方案

1.做一个高速接口,比如PCIE、以太网;

2.做一个低速接口,比如RS232;

3.存在BRAM中。

高速接口的问题是过于复杂,首先需要等接口完成(设计、验证通过)才能开始测试,其次引入新的接口,也引入了新的问题源;低速接口的问题是速度过慢,可能内部的逻辑需要工作在100MHz以上但接口速度只有几KHz,而等待低速接口的数据也无法验证完整的数据流工作。毕竟,FPGA更适合连续的流水线工作而不是等待。

所以BRAM的方案成了一个不错的选择。将数据存在BRAM中,读取BRAM中数据的操作,数据时钟可以直接使用真实时钟,数据也可以连续输出。由于测试工程通常都不是完整工程,BRAM资源通常也不是大问题。

不过BRAM也带来一个问题:数据源的更新。如果需要更换数据源,该怎么办呢?

1.重新跑工程生成bit文件,缺点是太麻烦了,而且重跑工程有时序不过的风险;

2.在线更新BRAM内容。

由于大部分FPGA的BRAM都是双口RAM,所以可以利用BRAM的一个端口来更新BRAM内容,另一个端口来读取BRAM内容作为测试数据源。暂时看起来是个不错的方案。

那么,更新BRAM,应该如何操作呢?

1.如果有MCU的接口或者RS232接口,可以通过这些接口进行更新;这个方案最大的问题是,不是每个设计都有这个接口;

2.JTAG;考虑到这个应用方案,更多的是在测试环境,通常可以使用JTAG。那么是否可以考虑用JTAG来实现这个功能?当然是可以的。

用过Quartus的童鞋,也许会知道Quartus提供了一个功能:In-System Memory Content Editor。

Altera提供了基于JTAG的双口BRAM更新数据的方案,所以如果使用Altera的FPGA,只要能使用JTAG,就可以使用这个功能来更新BRAM的数据。

好了,铺垫这么多,终于说到主题了。Vivado,截至2016.2,还没有这个功能。至于ISE,一直都没有这个功能。

所以使用Xilinx平台的FPGA,想利用BRAM来提供测试数据源,就比较麻烦。

解决方案,就是用VIO来实现这功能。更新BRAM内容的过程,速度通常不重要,更新完成后才会进行测试。JTAG和VIO的低速,足够实现这个功能,而且实际实现,最慢也就在几秒完成,完全没有问题。

首先看一下双口RAM。Xilinx的BRAM提供两种双口RAM方案:简单双端口和真双端口。

简单双端口是A端口只作为写入,B端口只作为读取;A端口无法读取,B端口无法写入。

真双端口,A、B两个端口都可以进行独立的读写操作,没有差别。

下面用真双端口作为例子。地址线8bit,输入输出端口16bit、启用EN功能。

tdpram your_instance_name (

.clka(clka), // input wire clka

.ena(ena), // input wire ena

.wea(wea), // input wire [0 : 0] wea

.addra(addra), // input wire [7 : 0] addra

.dina(dina), // input wire [15 : 0] dina

.douta(douta), // output wire [15 : 0] douta

.clkb(clkb), // input wire clkb

.enb(enb), // input wire enb

.web(web), // input wire [0 : 0] web

.addrb(addrb), // input wire [7 : 0] addrb

.dinb(dinb), // input wire [15 : 0] dinb

.doutb(doutb) // output wire [15 : 0] doutb

);

BRAM的B组端口接口可以改为只读控制,用来为后续逻辑电路提供数据源。

.clkb ( clk ),

.enb ( BRAM_ENB ),

.web ( 1'b0 ),

.addrb ( BRAM_ADDR_B ),

.dinb ( 16'd0 ),

.doutb ( BRAM_DOUTB )

打开enb端口,再控制地址线,就可以连续的提供数据输出。

至于A端口,连接到VIO上。

VIO_BRAM VIO_BRAM_UNIT (

.clk ( clk ),

.probe_in0 ( BRAM_DOUTA ),

.probe_out0 ( BRAM_ENA ),

.probe_out1 ( BRAM_WEA ),

.probe_out2 ( BRAM_ADDRA ),

.probe_out3 ( BRAM_DINA ),

.probe_out4 ( BRAM_ENB )

);

tdpram tdpram_uut (

.clka ( clk ),

.ena ( BRAM_ENA ),

.wea ( BRAM_WEA ),

.addra ( BRAM_ADDRA ),

.dina ( BRAM_DINA ),

.douta ( BRAM_DOUTA ),

这样,BRAM的A端口就完全的连接到VIO上了。通过VIO的操作,就可以实现BRAM的读写更新。

为了方便观察,可以加入一个ILA,来观察BRAM输出数据的改变。

ILA_0 ILA_UNIT (

.clk ( clk ),

.probe0 ( BRAM_ADDR_B ),

.probe1 ( BRAM_DOUTB )

);

这样工程的主体就完成了。这里只是一个demo,所以BRAM送出的数据源送往ILA,便于观察;真实条件下连接后续设计即可。工程还需要连接好时钟。由于Vivado的VIO不再提供异步端口(ISE的VIO可以提供异步端口),所以VIO的时钟,BRAM A、B端口时钟和ILA的时钟,都可以使用同一个时钟。这样也没有跨时钟问题。

完成设计之后就可以让Vivado生成bit文件了。生成好以后,下载到FPGA芯片中,打开BRAM B端口的ENB信号。由于BRAM IP在配置的时候默认选择全0参数,所以读取出来的数据全是0。

使用模拟模式进行查看更方便,可以看到作为地址的count已经周期性读取数据,输出数据全部都是0。下面用Tcl来更换数据。

首先,需要在Vivado的Tcl console界面中输入pwd,确认一下当前路径。通常,这个起始路径都需要切换一下。切换到Tcl脚本的路径下,输入source b0.tcl进行更新。

需要注意的是,Vivado Tcl console中source一个Tcl脚本中,最好不要有交互操作。似乎是Vivado GUI没有开发交互接口,source一个Tcl脚本过程中由于source没有结束会屏幕输入命令框,但是脚本中又会等待输入框命令的交互输入,这样最终的结果就是无法做任何操作的锁死。正在运行b0.tcl进行更新bram数据

更新完毕之后利用ILA来查看数据,可以看到BRAM B端口读取的数据已经不全是0了。b0.tcl的内容就是将地址作为数据写入bram中

for {set i 0} {$i < 256} {incr i} {

set j [format %x $i]

vbram_write h$j h$j

}

代码功能比较简单,就是将地址从10进制转为16进制然后作为数据和地址进行bram的写入处理。具体vio操作隐藏在vbram_write 这个proc中。

需要注意的是BRAM本身会带有一个周期的读取延迟,IP设置中又加了一级读取延迟,所以地址和读取数据之间有2的差值。count会比data大2。

下面利用b1.tcl来更新bram的数据,这个脚本的不同在于,数据不是地址,而是255-地址,这样得到的数据应该是个递减的数据。

for {set i 0} {$i < 256} {incr i} {

set j [format %x $i]

set l [format %x [expr 255-$i]]

vbram_write h$j h$l

}

可以看到更新后的输出,地址依然是上升的,但是数据是对称下降。这样就完成了数据更新。

整个流程走下来,用起来还是很方便的。而且bram的数据也是可读可写的,所以还有机会开发出更多的玩法。

上面的讨论介绍了更新BRAM方案的框架,但是忽略了一个问题,就是vbram_write这个proc。

整个使用的demo中,可能最麻烦的地方就是这个vbram_write应该如何写了。这里描述一下大概步骤。

1.在vio中连接了BRAM A端口的相关信号到VIO中。

2.由于使用了vio,所以就使用了vld,这样Vivado会自动生成ltx文件,来记录vld中的所有信号。通常,记录的信号名和verilog中的信号名是一样的。推荐查看ltx文件来确认一下。

3.由于Vivado每一个GUI的操作都有一个Tcl命令来对应,所以所有VIO的操作都可以找到对应的操作命令,从而实现用Tcl来控制BRAM A端口的每一个端口。

4.每个端口的命令进行组合,就可以拼装出完整的BRAM读写控制命令。将这些命令都放在一个Tcl脚本中,使用前先source这个脚本,就可以实现需要的功能。

原理描述以来很简单,但如果没有做过的话,第一次还是会花很多时间。这里就推荐脚本的功能了。可以写一个Tcl的脚本,自动完成上述功能,生成一个最终的Tcl脚本。这样可以大大的简化使用。这也就是Tcl在FPGA开发中深度结合EDA工具来做应用的一个例子。XTWL TPCL:浅谈FPGA开发中的Perl,Tcl和Python​zhuanlan.zhihu.com

由于整套操作需要使用交互,所以不会在Vivado GUI界面下完成,而是使用Shell界面。输入bit文件和ltx的路径后,Tcl会自动读取ltx文件来生成需要的命令,并确认BRAM的控制端口信号。完成后会自动生成bram控制命令,直接source后就可以使用。需要手动完成的代码只有b0.tcl和b1.tcl这两个文件而已。整体工作量被大幅度简化了。关于这个Tcl脚本和完整的使用,如果有兴趣准备好工程的,可以私信本人,一同研究一下玩法。

当然,这样使用VIO是一个另类的做法,或者说是一个早期的做法。现在,Xilinx已经推出了JTAG AXI的ip,将JTAG信号以AXI信号总线的形式送入FPGA内部。这个官方的方案当然更有保证。也是值得推荐的一种做法。

更多关于FPGA的分享,可以参考知乎专栏:FPGA CodingFPGA Coding​zhuanlan.zhihu.com

你可能感兴趣的:(vivado的vio怎么使用)