跳转至

Vivado 开发环境中的仿真

在硬件设计阶段,仿真是非常重要的调试手段。通过仿真可以看到处理器内部的信号情况,从而找出错误所在。在绝大多数的情况下,如果仿真正确了,特别是实现后加入延时的仿真正确了,实际放到板子上的时候往往也是正确的。学会仿真是进行硬件开发的重要能力。

thinpad_top 项目中的仿真代码

在 thinpad_top 的项目中,已经内置了调试仿真文件,具体在 thinpad_top.srcs/sim_1/new 目录中。该目录的几个模块文件是硬件的仿真。

这里面最重要的就是 tb.sv 文件,这是实验环境仿真的模块。这个仿真模块将其它的各个模块集合在一起,共同给设计的处理器提供输入输出信号,以及外设的行为模拟。在绝大多数情况下,这些模块能够正确模拟真正硬件的执行,也正因为如此,在设计实验处理器的时候可以使用这个模块来帮助验证设计的正确性。在仿真的环境下,可以通过仿真的过程给出各个信号随着时钟的变化过程,依据这样的信息就可以找出设计中的逻辑问题。因此,学会仿真,修改 tb.sv 文件来驱动所设计处理器的执行是一项非常重要的技能。与 thinpad_top.v 文件一样,同学们也一定要仔细研读 tb.sv 源代码中的内容,理解其中的仿真动作含义。

tb.sv 的源代码分为三个部分。

第一个部分为信号的定义。相对于设计的处理器模块来说,这部分信号定义的输入和输出正好和 thinpad_top 模块的输入和输出相反。这一点也很好理解,因为在仿真的时候是由 tb 驱动 thinpad_top 模块执行的,他们之间的关系如下图所示。

test_bench module

上图中,DUT 就是被测试的模块,右面就是 testbench 模块,输入输出就通过 DUT inputs 和 DUT outputs 联系在一起。

下面是信号定义的部分:

`timescale 1ns / 1ps
module tb;

wire clk_50M, clk_11M0592;
reg push_btn = 0;         //BTN5手动时钟按钮开关,带消抖电路,按下时为1
reg reset_btn = 0;         //BTN6手动复位按钮开关,带消抖电路,按下时为1
reg [3:0]  touch_btn;  //BTN1~BTN4,按钮开关,按下时为1
reg [31:0] dip_sw;     //32位拨码开关,拨到"ON"时为1
wire [15:0] leds;       //16位LED,输出时1点亮
wire [7:0]  dpy0;       //数码管低位信号,包括小数点,输出1点亮
wire [7:0]  dpy1;       //数码管高位信号,包括小数点,输出1点亮
wire txd;  //直连串口发送端
wire rxd;  //直连串口接收端
wire [31:0] base_ram_data; //BaseRAM数据,低8位与CPLD串口控制器共享
wire [19:0] base_ram_addr; //BaseRAM地址
wire [3:0] base_ram_be_n;  //BaseRAM字节使能,低有效。如果不使用字节使能,请保持为0
wire base_ram_ce_n;       //BaseRAM片选,低有效
wire base_ram_oe_n;       //BaseRAM读使能,低有效
wire base_ram_we_n;       //BaseRAM写使能,低有效
wire [31:0] ext_ram_data; //ExtRAM数据
wire [19:0] ext_ram_addr; //ExtRAM地址
wire [3:0] ext_ram_be_n;  //ExtRAM字节使能,低有效。如果不使用字节使能,请保持为0
wire ext_ram_ce_n;       //ExtRAM片选,低有效
wire ext_ram_oe_n;       //ExtRAM读使能,低有效
wire ext_ram_we_n;       //ExtRAM写使能,低有效
wire [22:0] flash_a;      //Flash地址,a0仅在8bit模式有效,16bit模式无意义
wire [15:0] flash_d;      //Flash数据
wire flash_rp_n;         //Flash复位信号,低有效
wire flash_vpen;         //Flash写保护信号,低电平时不能擦除、烧写
wire flash_ce_n;         //Flash片选信号,低有效
wire flash_oe_n;         //Flash读使能信号,低有效
wire flash_we_n;         //Flash写使能信号,低有效
wire flash_byte_n;       //Flash 8bit模式选择,低有效。在使用flash的16位模式时请设为1
wire uart_rdn;           //读串口信号,低有效
wire uart_wrn;           //写串口信号,低有效
wire uart_dataready;     //串口数据准备好
wire uart_tbre;          //发送数据标志
wire uart_tsre;          //数据发送完毕标志

可以看到,上述的代码与实际的 thinpad_top 的代码信号上是一一对应的,正好用这个代码来驱动 thinpad_top 实现的模块。

第二部分是存储初始化以及开发板的动作模拟。紧跟着上述信号定义模块是内存,FLASH 的初始化部分,以及一个 initial 函数。这部分是同学们可以调节的部分。这部分的代码如下。

//Windows需要注意路径分隔符的转义,例如"D:\\foo\\bar.bin"
parameter BASE_RAM_INIT_FILE = "/tmp/main.bin"; //BaseRAM初始化文件,请修改为实际的绝对路径
parameter EXT_RAM_INIT_FILE = "/tmp/eram.bin";    //ExtRAM初始化文件,请修改为实际的绝对路径
parameter FLASH_INIT_FILE = "/tmp/kernel.elf";    //Flash初始化文件,请修改为实际的绝对路径
assign rxd = 1'b1; //idle state
initial begin 
    //在这里可以自定义测试输入序列,例如:
    dip_sw = 32'h2;
    touch_btn = 0;
    reset_btn = 1;
    #100;
    reset_btn = 0;
    for (integer i = 0; i < 20; i = i+1) begin
        #100; //等待100ns
        push_btn = 1; //按下手工时钟按钮
        #100; //等待100ns
        push_btn = 0; //松开手工时钟按钮
    end
    // 模拟PC通过串口发送字符
    cpld.pc_send_byte(8'h32);
    #10000;
    cpld.pc_send_byte(8'h33);
end

可以看到第一部分可以以本地文件(二进制)来初始化内存里面的内容以及 flash 里面的内容。如果调试最后一个大实验,这里可以使用监控程序的二进制内容来填写对应的存储介质。

后面的 initial 函数可以模拟在开发板上的动作。这样,在进行仿真调试的时候,可以修改这部分内容来模拟仿真时候的按钮动作。具体方法是使用语句:#100 xxx 就是延迟 100 个时间单位后做事情。

例如:

#100 reset_btn = 1;
#105 reset_btn = 0;

这就模拟按下一个 reset_btn 的动作。其它的动作可以按照上述的例子进行仿写。代码已经包含有对应的注释,同学们可以照着来修改。

第三个部分是将各个外设的功能仿真部分和 thinpad 的设计模块组合在一起,完成一个仿真系统。连接的模块包括以下的各个部分:

可以看到,dut(Design Under Test)就是待测的模块,就是 thinpad_top 的一个实例化。其它的是外围的仿真模型,包括串口,静态内存,FLASH 等。在顶层项目中,这些内容都已经被连接完成了,直接使用即可。

Vivado 开发环境的仿真

Vivado 具有非常丰富的仿真手段,包括行为仿真以及包含时延信息的综合后或者实现后仿真。行为仿真的计算量比较小,也是大家平时经常用的仿真流程。在这里就简单介绍一下行为仿真的使用方法。由于 testbench 已经由 thinpad_top 的顶层项目已经提供了,大部分情况下只需要修改 tb.sv 中的代码就可以对所设计的处理器进行仿真测试。

开启仿真,点击 Run Simulation。

选择 Run Behavioral Simulation 就可以进入行为仿真。(另外,仿真的时候会产生非常大的文件。右键点击 Simulation,选择 Reset Behavioral Simulation 可以删除仿真的时候产生的临时文件。)

等待开启行为仿真。

进入行为仿真的界面,这个界面非常重要,同学们会经常使用到。

把这个界面分成各个部分。

① 选择对象的范围;

② 某一个具体对象的信号;

③ 波形图的信号;

④ 波形图;

⑤ 命令行窗口(这里是可以敲入命令的,大部分情况下不需要,但是有的时候是必须的);

⑥ 仿真的命令菜单。

下面对上面组成部分进行逐一的解释。

① 选择对象的范围:

这里可以选择需要观察的对象。具体的对象的名称是与源文件中一致的。这里选择的是被测对象 dut 里面的一个模块 mem_uart,对应的源文件是 mem_controller.v 代码。选择这个对象之后,在窗格②中就显示了对应的对象的信号。对象的信号来自于源代码,包括了内部的信号以及对外的输入输出的信号。

② 某一个具体对象的信号:

可以看到,上述就是对象 mem_uart 内部的信号。如果希望把某一个信号加入到波形列表中,可以右键点击对应的信号,选择加入观察波形中。

上面的箭头指示了把信号加入到波形窗口中。下面的箭头指示对应信号的值是以什么样进制显示,可以二进制,十进制,十六进制等。

③ 波形图的信号:

④ 波形图:

波形图的信号和波形图应该联合在一起看。左边是波形图中显示的信号,右面就是对应信号的波形图。在波形图的最上面给出了显示波形的时间序列关系。右键点击波形图信号,可以查看对应信号的操作,包括可以删除信号,也可以给信号改名等。在右上角有保存的命令,可以保存当前波形图的配置,这样再下一次进行仿真的时候就会调入上一次仿真已经设置好的信号,方便调试。

右边的波形图就显示了各个信号随着时间变化的过程。这里面需要值得注意的是一些特殊的值,例如蓝色的表示 Z,即高阻态。红色的代表 X,即未定义值,一般也就代表了模块中未初始化的部分,或者代表了代码中有错误。应该要特别注意按下 reset 按钮之后出现的红色信号的部分,往往这意味着代码的错误。

⑤ 命令行窗口(这里是可以敲入命令的,大部分情况下不需要,但是有的时候是必须的):

可以看到,命令行窗口。这里面的 Type a Tcl command here 是可以敲入的命令行。实际上,在这个命令行窗口上面显示的就是通过 GUI 界面自动键入的命令,可以启动仿真的过程。用户也可以自己在这个命令行上键入命令。大部分情况下并不需要自己键入命令,通过菜单选择即可。但是,在某些情况下需要,后面会有例子说明这一点。

⑥ 仿真的命令菜单

菜单上的命令都有相应的提示。 为重启等待执行。 为不断仿真执行,直到后面的暂停按钮 被按下。 为重新启动仿真并执行 1000ns。 从当前状态开始,仿真执行 10us,其中时间长度和时间单位都可以调整。

常用的在 tcl 命令行上键入的命令:

add_wave {{/tb/base1/mem_array0[5]}} -name mem5

把信号 /tb/base1/mem_array0[5] 添加到波形窗口中,这里的信号实际上是内存模块 base1 的第一片内存位置 5 的内存单元。之所以要这么做的原因是内存模块的数据量太大了,都加进去会卡死 Vivado。最后的参数 -name 是给这个参数进行命名,方便用户阅读。

save_wave_config {G:/thinpad_top/tb_behav.wcfg}

使用上述命令就可以把当前仿真配置(哪些信号被加入到波形窗口)保存,点击上面的保存按钮也可以达到这个目的,在 tcl 命令行中也可以看到该命令。

下面的脚本把内存的前几个位置加入到波形中,同学们可以直接拷贝下面的命令到命令行窗口,就可以将 Base 内存前 64 个字节放入到命令行窗口中。

add_wave {{/tb/base1/mem_array0[0]}} -name bmem0
add_wave {{/tb/base1/mem_array1[0]}} -name bmem1
add_wave {{/tb/base2/mem_array0[0]}} -name bmem2
add_wave {{/tb/base2/mem_array1[0]}} -name bmem3
add_wave {{/tb/base1/mem_array0[1]}} -name bmem4
add_wave {{/tb/base1/mem_array1[1]}} -name bmem5
add_wave {{/tb/base2/mem_array0[1]}} -name bmem6
add_wave {{/tb/base2/mem_array1[1]}} -name bmem7
add_wave {{/tb/base1/mem_array0[2]}} -name bmem8
add_wave {{/tb/base1/mem_array1[2]}} -name bmem9
add_wave {{/tb/base2/mem_array0[2]}} -name bmem10
add_wave {{/tb/base2/mem_array1[2]}} -name bmem11
add_wave {{/tb/base1/mem_array0[3]}} -name bmem12
add_wave {{/tb/base1/mem_array1[3]}} -name bmem13
add_wave {{/tb/base2/mem_array0[3]}} -name bmem14
add_wave {{/tb/base2/mem_array1[3]}} -name bmem15
add_wave {{/tb/base1/mem_array0[4]}} -name bmem16
add_wave {{/tb/base1/mem_array1[4]}} -name bmem17
add_wave {{/tb/base2/mem_array0[4]}} -name bmem18
add_wave {{/tb/base2/mem_array1[4]}} -name bmem19
add_wave {{/tb/base1/mem_array0[5]}} -name bmem20
add_wave {{/tb/base1/mem_array1[5]}} -name bmem21
add_wave {{/tb/base2/mem_array0[5]}} -name bmem22
add_wave {{/tb/base2/mem_array1[5]}} -name bmem23
add_wave {{/tb/base1/mem_array0[6]}} -name bmem24
add_wave {{/tb/base1/mem_array1[6]}} -name bmem25
add_wave {{/tb/base2/mem_array0[6]}} -name bmem26
add_wave {{/tb/base2/mem_array1[6]}} -name bmem27
add_wave {{/tb/base1/mem_array0[7]}} -name bmem28
add_wave {{/tb/base1/mem_array1[7]}} -name bmem29
add_wave {{/tb/base2/mem_array0[7]}} -name bmem30
add_wave {{/tb/base2/mem_array1[7]}} -name bmem31
add_wave {{/tb/base1/mem_array0[8]}} -name bmem32
add_wave {{/tb/base1/mem_array1[8]}} -name bmem33
add_wave {{/tb/base2/mem_array0[8]}} -name bmem34
add_wave {{/tb/base2/mem_array1[8]}} -name bmem35
add_wave {{/tb/base1/mem_array0[9]}} -name bmem36
add_wave {{/tb/base1/mem_array1[9]}} -name bmem37
add_wave {{/tb/base2/mem_array0[9]}} -name bmem38
add_wave {{/tb/base2/mem_array1[9]}} -name bmem39
add_wave {{/tb/base1/mem_array0[10]}} -name bmem40
add_wave {{/tb/base1/mem_array1[10]}} -name bmem41
add_wave {{/tb/base2/mem_array0[10]}} -name bmem42
add_wave {{/tb/base2/mem_array1[10]}} -name bmem43
add_wave {{/tb/base1/mem_array0[11]}} -name bmem44
add_wave {{/tb/base1/mem_array1[11]}} -name bmem45
add_wave {{/tb/base2/mem_array0[11]}} -name bmem46
add_wave {{/tb/base2/mem_array1[11]}} -name bmem47
add_wave {{/tb/base1/mem_array0[12]}} -name bmem48
add_wave {{/tb/base1/mem_array1[12]}} -name bmem49
add_wave {{/tb/base2/mem_array0[12]}} -name bmem50
add_wave {{/tb/base2/mem_array1[12]}} -name bmem51
add_wave {{/tb/base1/mem_array0[13]}} -name bmem52
add_wave {{/tb/base1/mem_array1[13]}} -name bmem53
add_wave {{/tb/base2/mem_array0[13]}} -name bmem54
add_wave {{/tb/base2/mem_array1[13]}} -name bmem55
add_wave {{/tb/base1/mem_array0[14]}} -name bmem56
add_wave {{/tb/base1/mem_array1[14]}} -name bmem57
add_wave {{/tb/base2/mem_array0[14]}} -name bmem58
add_wave {{/tb/base2/mem_array1[14]}} -name bmem59
add_wave {{/tb/base1/mem_array0[15]}} -name bmem60
add_wave {{/tb/base1/mem_array1[15]}} -name bmem61
add_wave {{/tb/base2/mem_array0[15]}} -name bmem62
add_wave {{/tb/base2/mem_array1[15]}} -name bmem63

以下是 Ext 内存的前 64 个字节:

add_wave {{/tb/ext1/mem_array0[0]}} -name emem0
add_wave {{/tb/ext1/mem_array1[0]}} -name emem1
add_wave {{/tb/ext2/mem_array0[0]}} -name emem2
add_wave {{/tb/ext2/mem_array1[0]}} -name emem3
add_wave {{/tb/ext1/mem_array0[1]}} -name emem4
add_wave {{/tb/ext1/mem_array1[1]}} -name emem5
add_wave {{/tb/ext2/mem_array0[1]}} -name emem6
add_wave {{/tb/ext2/mem_array1[1]}} -name emem7
add_wave {{/tb/ext1/mem_array0[2]}} -name emem8
add_wave {{/tb/ext1/mem_array1[2]}} -name emem9
add_wave {{/tb/ext2/mem_array0[2]}} -name emem10
add_wave {{/tb/ext2/mem_array1[2]}} -name emem11
add_wave {{/tb/ext1/mem_array0[3]}} -name emem12
add_wave {{/tb/ext1/mem_array1[3]}} -name emem13
add_wave {{/tb/ext2/mem_array0[3]}} -name emem14
add_wave {{/tb/ext2/mem_array1[3]}} -name emem15
add_wave {{/tb/ext1/mem_array0[4]}} -name emem16
add_wave {{/tb/ext1/mem_array1[4]}} -name emem17
add_wave {{/tb/ext2/mem_array0[4]}} -name emem18
add_wave {{/tb/ext2/mem_array1[4]}} -name emem19
add_wave {{/tb/ext1/mem_array0[5]}} -name emem20
add_wave {{/tb/ext1/mem_array1[5]}} -name emem21
add_wave {{/tb/ext2/mem_array0[5]}} -name emem22
add_wave {{/tb/ext2/mem_array1[5]}} -name emem23
add_wave {{/tb/ext1/mem_array0[6]}} -name emem24
add_wave {{/tb/ext1/mem_array1[6]}} -name emem25
add_wave {{/tb/ext2/mem_array0[6]}} -name emem26
add_wave {{/tb/ext2/mem_array1[6]}} -name emem27
add_wave {{/tb/ext1/mem_array0[7]}} -name emem28
add_wave {{/tb/ext1/mem_array1[7]}} -name emem29
add_wave {{/tb/ext2/mem_array0[7]}} -name emem30
add_wave {{/tb/ext2/mem_array1[7]}} -name emem31
add_wave {{/tb/ext1/mem_array0[8]}} -name emem32
add_wave {{/tb/ext1/mem_array1[8]}} -name emem33
add_wave {{/tb/ext2/mem_array0[8]}} -name emem34
add_wave {{/tb/ext2/mem_array1[8]}} -name emem35
add_wave {{/tb/ext1/mem_array0[9]}} -name emem36
add_wave {{/tb/ext1/mem_array1[9]}} -name emem37
add_wave {{/tb/ext2/mem_array0[9]}} -name emem38
add_wave {{/tb/ext2/mem_array1[9]}} -name emem39
add_wave {{/tb/ext1/mem_array0[10]}} -name emem40
add_wave {{/tb/ext1/mem_array1[10]}} -name emem41
add_wave {{/tb/ext2/mem_array0[10]}} -name emem42
add_wave {{/tb/ext2/mem_array1[10]}} -name emem43
add_wave {{/tb/ext1/mem_array0[11]}} -name emem44
add_wave {{/tb/ext1/mem_array1[11]}} -name emem45
add_wave {{/tb/ext2/mem_array0[11]}} -name emem46
add_wave {{/tb/ext2/mem_array1[11]}} -name emem47
add_wave {{/tb/ext1/mem_array0[12]}} -name emem48
add_wave {{/tb/ext1/mem_array1[12]}} -name emem49
add_wave {{/tb/ext2/mem_array0[12]}} -name emem50
add_wave {{/tb/ext2/mem_array1[12]}} -name emem51
add_wave {{/tb/ext1/mem_array0[13]}} -name emem52
add_wave {{/tb/ext1/mem_array1[13]}} -name emem53
add_wave {{/tb/ext2/mem_array0[13]}} -name emem54
add_wave {{/tb/ext2/mem_array1[13]}} -name emem55
add_wave {{/tb/ext1/mem_array0[14]}} -name emem56
add_wave {{/tb/ext1/mem_array1[14]}} -name emem57
add_wave {{/tb/ext2/mem_array0[14]}} -name emem58
add_wave {{/tb/ext2/mem_array1[14]}} -name emem59
add_wave {{/tb/ext1/mem_array0[15]}} -name emem60
add_wave {{/tb/ext1/mem_array1[15]}} -name emem61
add_wave {{/tb/ext2/mem_array0[15]}} -name emem62
add_wave {{/tb/ext2/mem_array1[15]}} -name emem63

以上是通过 Vivado 进行仿真的时候一些命令和使用的方法。这里的方法是一些基本的方法,其它的一些使用方法同学们可以结合实际的使用例子来学习。


最后更新: 2024年9月8日
作者:Jiajie Chen (15.9%), Youyou Lu (26.3%), gaoyichuan (54.05%), Kang Chen (0.29%), kusile (3.47%)