基于modelsim-SE的简单仿真流程
编写RTL功能代码
要进行功能仿真,首先得用需要仿真的模块,也就是RTL功能代码,简称待测试的模块,该模块也就是在设计下载到FPGA的电路。一个电路模块想要有输出,就得有输出,数字电路也是一样的,时钟和复位信号是必不可少的测试激励信号之一,同时可能还包括控制信号、数据信号。这里总结一下,一般系统中包括的测试激励信号主要为四大类:
- 系统信号(时钟、时钟使能信号、复位信号等);
- 控制信号(使能信号、片选信号、握手信号,状态机控制信号等);
- 数据信号(并行接口数据信号、串行接口数据信号等);
- 地址信号(模块的地址信号、内部寄存/存储器地址信号等)。
而对于测试结果,在放在不同的测试激励信号的条件下分析,不同的测试激励产生相应的测试结果,通过分析就可以知道相应的测试结果与目标结果是否一致,如果不一致便进行RTL的修改,如果一致则修改测试激励信号,遍历完不同情况下的测试激励,完成整体100%的功能测试,才能确保RTL功能模块的设计功能完整性。
这里需要注意一点,功能仿真正确不一定能确保最后综合、布局布线后下载到FPGA产生的功能结果正确,但是功能仿真不正确,FPGA产生的功能结果一定不正确!所以功能仿真是很必要的!
本实验需要设计一个8bit的计数器,功能很简单,该计数器在每个时钟的上升沿进行加一并将结果输出,当计数到255的时候返回到0重新计数,以此循环,模块接口如下所示。
测试激励信号是时钟和复位信号,时钟信号为50M,复位信号为低电平有效。测试结果为8bit的计数值,范围为0~255,如果从波形上看是一个锯齿波。
module counter( clk, reset_n, counter_out ); input clk; input reset_n; output [7:0]counter_out; reg [7:0]counter_reg; assign counter_out = counter_reg; always @(posedge clk or negedge reset_n) begin if(reset_n == 1‘b0) begin counter_reg <= 8‘d0; end else begin counter_reg <= counter_reg + 1‘b1; end end endmodule |
编写testbench代码
TestBench基本概念:测试激励->待测RTL功能模块->观察比对波形/在终端打印或生产文本/自动对比输出结果。
TestBench编写三步曲:
- 对被测试设计的顶层接口进行例化;
- 给被测试设计的输入接口添加激励信号;
- 判断被测试设计的输出想要是否满足设计要求;
TestBench内容主要分为:
- 时钟的产生;
- 复位的产生;
- 控制信号的产生;
- 其他激励信号的产生(用户自定义 )。
相应于被测试模块的输入激励设置为reg型,输出相应设置为wire类型,双向端口inout在测试中需要进行处理。
双向端口inout的设置方法:
为双向端口inout设置中间变量inout_reg作为该inout的输出寄存,inout口在testbench中要定义为wire型变量,然后用输出使能控制传输方向。
inout pin_dir_port; wire pin_dir_port; reg pin_dir_port_reg; reg pin_dir_port_oe; assign pin_dir_port = pin_dir_port_oe ? pin_dir_port_reg:1‘bz; |
用pin_dir_port_oe控制端口数据方向,并利用中间变量寄存器改变其值,等于两个模块之间用inout双向口互连。
本实验的testbench主要是为8bit的计数器产生测试激励信号,即产生50M的时钟信号clk和复位信号reset_n。
一个完成的testbench文件内容主要包括三个部分:
- 仿真时间单位;
- 待测试模块信号映射;
- 激励信号产生;
仿真时间单位
如上图标注所示:`timescale 1 ns/ 10 ps表示,时间单位为1ns,精度为10ps,时间单位应该大于时间精度,不然仿真过程将会报错,同时仿真的时间精度越小,仿真结果的波形的可视精度就越小,对仿真花费时间的要求越高,精度越小,仿真时间越长。
待测试模块信号映射
将测试功能模块映射到testbench中,如图中的标注2所示,一般采用如下格式进行映射,其中测试功能模块的输入信号在testbench中定义为reg信号,因为要在always中进行赋值操作,而测试功能模块的输出信号定义为wire信号,该信号只用于观察,不用于赋值操作。
测试功能模块 例化测试功能模块名( .输入信号(测试激励信号), .输出信号(定义为wire型信号进行观察) ); |
激励信号产生
激励信号的产生涉及到两个阶段:初始化阶段和赋值操作阶段。初始化阶段在initial模块中进行操作,赋值操作阶段在always模块进行,如下格式。$display为显示语句,括号内双引号内的内容为要显示的内容,该内容将会在modelsim软件的脚本显示窗口中显示出来。
Initial模块只执行一次。
initial begin // code that executes only once // insert code here --> begin 对激励信号进行初赋值操作 // --> end $display("Running testbench"); end |
在初始化完成后,需要对激励信号进行赋值操做,赋值操作在always中进行,always模块一直在执行。本实验时钟信号为50M,定义的时间单位为1ns,所以在每个10ns进行一次翻转,复位信号为低电平有效,所以在初始化的时候设置为低电平,在always模块里面设置为高电平。格式如下所示:
always // optional sensitivity list // @(event1 or event2 or .... eventn) begin // code executes for every event on sensitivity list // insert code here --> begin 激励信号赋值操作,持续执行 // --> end end |
testbench代码:
`timescale 1 ns/ 10 ps module counter_tst(); // constants // general purpose registers // test vector input registers reg clk; reg reset_n; // wires wire [7:0]counter_out; counter counter_1( .clk (clk), .reset_n (reset_n), .counter_out(counter_out) ); initial begin // code that executes only once // insert code here --> begin clk<=1‘b0; reset_n<=1‘b0; // --> end $display("Running testbench"); end always // optional sensitivity list // @(event1 or event2 or .... eventn) begin // code executes for every event on sensitivity list // insert code here --> begin #10 clk<=~clk; reset_n<=1‘b1; // --> end end endmodule |
创建工程
运行 ModelSim,方法是点击开始->程序->ModelSim SE->ModelSim 或双击桌面上的快捷方式,会出现如下图所示的界面,如果上一次使用ModelSim 建立过工程,这时候会自动打开上一次所建立的工程,可以close project,具体操作:选中工程的文件,右击,在出现的栏目中选择close project。
点击 File->New->Project,会出现如下图所示的界面。
选择project后,会出现如下界面在 Project Name 中我们输入建立的工程名字为counter,在Project Location 中点击browse按键,选择工程保存的路径,注意 ModelSim 不能为一个工程自动建立一个目录,这里最好是选择已经创建好的一个工程文件夹作为目录,在 Default Library Name 中为我们的设计编译到哪一个库中,这里使用默认值,这样,在编译设计文件后,在 Workspace 窗口的 Library中就会出现 work 库。这里我们输入完以后,点击 OK。
在点击OK按键后会出现选择仿真文件的界面,如下图所示,可以点击不同的图标来为工程添加不同的项目,点击 Create New File 可以为工程添加新建的文件, 点击 Add Existing File为工程添加已经存在的文件,点击 Create Simulation 为工程添加仿真,点击Create New Folder 可以为工程添加新的目录。这里我们点击Add Existing File;
这里将前面设计的counter.v文件和counter_tst.v文件包含进工程里面,点击browse按键,将文件包含进来,如下图所示,然后点击ok按键。
接着会在modelsim的工程工作窗口出现如下图所示,即已经将两个文件包含进来,其中在状态那一栏出现两个问号,表明文件包含进来了,还没有进行编译。