特权同学

玩转Zynq连载26——Vivado中PL的功能仿真

0
阅读(100) 评论(0)

玩转Zynq连载26——Vivado中PL的功能仿真

 

更多资料共享

腾讯微云链接:https://share.weiyun.com/5s6bA0s

百度网盘链接:https://pan.baidu.com/s/1XTQtP5LZAedkCwQtllAEyw

提取码:ld9c

1.jpg

 

 

 

 

 

腾讯微云链接:https://share.weiyun.com/5s6bA0s

百度网盘链接:https://pan.baidu.com/s/1XTQtP5LZAedkCwQtllAEyw

提取码:ld9c

 

1仿真验证概述

         仿真测试是FPGA设计流程中必不可少的步骤。尤其在FPGA规模和设计复杂性不断提高的今天,画个简单的原理图或写几行代码直接就可以上板调试的轻松活儿已经一去不复返。一个正规的设计需要花费在验证上的工作量往往可能会占到整个开发流程的70%左右。验证我们通常分为仿真验证和板级验证,在设计初步完成功能甚至即将上板调试前,通过EDA仿真工具模拟实际应用进行验证是非常有效可行的手段,它能够尽早的发现设计中存在的各种大小bug,避免设计到了最后一步才返工重来。因此,仿真在整个验证中的重要性可见一斑。

         提到仿真,我们通常会提testbench的概念。所谓testbench,即测试平台,详细的说就是给待验证的设计添加激励,同时观察它的输出响应是否符合设计要求。如图所示,测试平台就是要模拟一个和待验证设计相连接的各种外围设备。

2.jpg

图设计与验证

         初学者在刚接触仿真这个概念的时候,可能以为仿真只是简单的用一些开发软件自带的波形发生器产生一些激励,然后观察一下最后的波形输出就完事了。但是对于大规模的设计,用波形产生激励是不现实的,观察波形的工作量也是可想而知的。例如,对于一个16位的输入总线,它可以有65536种组合,如果每次随机产生一种输入,那用波形岂不累死人。再说输出结果的观察,对应65536种输入的65536种输出,看波形肯定让人花眼缭乱。所以,testbench应该有更高效的测试手段。对于FPGA的仿真,使用波形输入产生激励是可以的,观察波形输出以验证测试结果也是可以的,波形也许是最直观的测试手段,但绝不是唯一手段。

         如图所示,设计的测试结果判断不仅可以通过观察对比波形,而且可以灵活的使用脚本命令将有用的输出信息打印到终端或者产生文本进行观察,也可以写一段代码让他们自动比较输出结果。总之,testbench的设计是多种多样的,它的语法也是很随意的,不像RTL级设计代码那么多讲究,它是基于行为级的语法,很多高级的语法都可以在脚本中使用。因为它不需要实现到硬件中,它是运行在PC机上的一段脚本,所以相对RTL级可以做得更容易更灵活一些。但是,使用Verilog的验证脚本也有很多需要设计者留意的地方,它是一种基于硬件语言但是又服务于软件测试的语言,所以它常常游离于并行和顺序之间让人琢磨不透。不过,只要掌握好了这些关键点,是可以很好的让它服务于我们的测试。

3.jpg

图验证输出

 

2 Testbench的设计

         Testbench的编写其实也没有想象中那么神秘,笔者简单的将其归纳为3个步骤。

①对被测试设计的顶层接口进行例化。

②给被测试设计的输入接口添加激励。

③判断被测试设计的输出响应是否满足设计要求。

相对而言,最后一步还要复杂一些,有时不一定只是简单的输出观察,可能还需要反馈一些输入值给待测试设计。

         例化的目的就是把待测试设计和testbench进行对接,和FPGA内部的例化是一个概念。那么如何进行例化呢?下面用一个简单实例来说明。

//待测试的设计

module fpga_design(

        clk,rst_n,a,b,c,d

    );

input clk;

input rst_n;

input a,b,c;

output d;

 

always @(posedgeclk or negedgerst_n) begin

    if(!rst_n) d <= 1’b0;

    else d <= a & b & c;

end

 

endmodule

    对于上面这个待测试的设计,testbench中的例化应该把input转换成reg,因为待测试设计的输入值是由testbench决定的。相应的output就应该转换成wire,因为待测试设计的输出值不是由testbench决定的。如果是inout端口,在例化中也是一个wire类型,在testbench中使用时和RTL代码设计中使用是一样的。

//例化待测试设计

reg clk;

reg rst_n;

reg a,b,c;

wire d;

 

fpga_design(

        .clk(clk),

.rst_n(rst_n),

.a(a),

.b(b),

.c(c),

.d(d)

    );

         对于激励的产生,只提最基本的时钟信号和复位信号的产生。时钟信号产生方式有很多,使用initial和always语句都是可以的。下面列出比较典型的两种产生方式供大家参考。

//时钟产生

parameter PERIOD = 20; //定义时钟周期为20ns,已定义“`timescale 1ns/1ps”

 

initial begin

    clk = 0;

    forever

        #(PERIOD/2) clk = ~clk;

end

 

//时钟产生

parameter PERIOD = 20; //定义时钟周期为20ns,已定义“`timescale 1ns/1ps”

 

always begin

    #(PERIOD/2) clk = 0;

    #(PERIOD/2) clk = 1;

end

         复位信号的产生也很简单,比较常用的做法是封装成一个task,直接在需要复位的时候调用即可。

//复位产生

initial begin

    reset_task(100);   //复位100ns,已定义“`timescale 1ns/1ps”

    ……

end

 

task reset_task;

input[15:0] reset_time; //复位时间

begin

    reset = 0;

    #reset_time;

    reset = 1;

end

endtask

         至于对测试的响应如何进行观察处理,这里不作太多描述,大家随便找本verilog语法方面的书籍都会有相应的行为级语法的部分,只要依葫芦画瓢就能学会。

         对于这个简单的设计,有a、b和c三个输入,他们相与的结果d每个时钟周期输出一次最新的结果。因此,我们可以预见,若想完全覆盖这个设计的测试分支,那么要产生8个不同的测试项,即分别改变a、b和c的取值,观察他们输出的结果是否符合预期。测试脚本的编写如下所示。

//复位产生

timescale 1ns/1ps

module tb_fpga_design;

 

//例化待测试设计

reg clk;

reg rst_n;

reg a,b,c;

wire d;

 

fpga_design(

        .clk(clk),

.rst_n(rst_n),

.a(a),

.b(b),

.c(c),

.d(d)

    );

 

initial begin

    reset_task(100);   //复位100ns,已定义“`timescale 1ns/1ps”

    @(posedgeclk); #2;

    a = 1’b0;

    b = 1’b0;

    c = 1’b0;

    @(posedgeclk); #2;

    a = 1’b0;

    b = 1’b0;

    c = 1’b1;

    @(posedgeclk); #2;

    a = 1’b0;

    b = 1’b1;

    c = 1’b0;

    @(posedgeclk); #2;

    a = 1’b0;

    b = 1’b1;

    c = 1’b1;

    @(posedgeclk); #2;

    a = 1’b1;

    b = 1’b0;

    c = 1’b0;

    @(posedgeclk); #2;

    a = 1’b1;

    b = 1’b0;

    c = 1’b1;

    @(posedgeclk); #2;

    a = 1’b1;

    b = 1’b1;

    c = 1’b0;

    @(posedgeclk); #2;

    a = 1’b1;

    b = 1’b1;

    c = 1’b1;

    @(posedgeclk); #2;

    $stop;

end

 

task reset_task;

input[15:0] reset_time; //复位时间

begin

    reset = 0;

    #reset_time;

    reset = 1;

end

endtask

 

//时钟产生

parameter PERIOD = 20; //定义时钟周期为20ns,已定义“`timescale 1ns/1ps”

 

always begin

    #(PERIOD/2) clk = 0;

    #(PERIOD/2) clk = 1;

end

 

endmodule

         使用这个脚本对设计进行仿真,我们观察结果输出,在8种不同的设计输入情况下,输出是否和预期一致。若一致,则可以继续后面的设计流程完成设计,若不一致,则设计中一定存在问题,需要查找问题原因并对设计进行修改直到仿真结果达到预期结果。

 

3 创建仿真文件

         以zstar_ex01为例,如图所示,选中Project Manager à Simulation Sources à sim_1,右键点击弹出菜单后,选中Add Sources…。

4.jpg

图新建仿真文件菜单

         接着如图所示,使用默认选项Add or create simulation sources。

5.jpg

图6.33 选择文件类型

         如图所示,单击Create File按钮。

6.jpg

图新建设计文件

         如图所示,设置创建的文件类型(File type)为Verilog;文件名(File name)为sim_zstar;文件路径(File location)为默认的<Local to Project>。

7.jpg

图设置新建文件名称和路径

         最后点击Finish按钮完成仿真测试文件的创建。

         随后弹出模块端口定义的界面,可以直接点击OK不做设定。

8.jpg

图模块端口定义

         如图所示,双击Simulation Sources下刚刚创建好的sim_zstar.v文件,里面也只有一个Verilog模块框架,有待内容填充。

9.jpg

图创建好的仿真测试源文件

         如图所示,将这个实例的仿真测试脚本写入这个文件中。

10.jpg

图仿真测试脚本

 

4功能仿真

         下面我们看看如何使用Vivado自带的仿真工具实现功能仿真。如图所示,单击Project Manager à Simulation àSimulation Settings,弹出选项卡中,设置目标仿真器(Target Simulator)为Vivado Simulator,仿真语言(Simulation language)为Mixed,仿真集(Simulation set)为sim_1,仿真顶层模块名(Simulation top module name)为sim_zstar。

11.jpg

图仿真设置

         如图所示,点击Run Simulation,接着弹出菜单中再点击Run Behavioral Simulation进行功能仿真。

12.jpg

图运行功能仿真

         弹出仿真界面如图所示,3个窗口从左到右依次是模块及层次显示窗口、信号显示窗口和波形窗口。

13.jpg

图仿真界面

         如图所示,点击Run All按钮。

14.jpg

图仿真运行按钮

         此时,如图所示,仿真运行中。

15.jpg

图仿真运行中

         如图所示,可以点击波形界面右上角的Float按钮,将波形界面从Vivado中独立出来,这样可以满屏显示波形,看到更多的波形细节。

16.jpg

图悬浮仿真波形界面

         如图所示,在仿真结束后,我们可以点击Zoom Fit按钮将所有的仿真波形缩放到可视界面中。

17.jpg

图6.45 全览仿真波形

         如图所示,这是该工程仿真的波形。beep信号一直是10%占空比的1Hz PWM信号。

18.jpg

图仿真波形