Verilator Pt.2: Basics of SystemVerilog verification using C++

在用Verilator写数字电路实验一头雾水,便找了一些资源补充学习 [来源如下]

代码解释块:

01 Long commands are for trolodytes

verilator -Wall --trace -cc alu.sv --exe tb_alu.cpp

–wall使能c++错误
–trace 使能波形
–cc把alu.sv文件转换成c++
–exe tb_alu.cpp告诉Verilator哪个文件是c++测试文件(testbench)

make -C obj_dir -f Valu.mk Valu //构建可执行文件,告诉make把工作目录更改为obj_dir,使用名为Valu.mk的构建文件,并构建为Valu的目标
./obj_dir/Valu //跑仿真,产生波形

在工作目录创建一个Makefile的文件,并粘贴以下内容:

MODULE=alu

.PHONY:sim
sim: waveform.vcd

.PHONY:verilate
verilate: .stamp.verilate

.PHONY:build
build: obj_dir/Valu

.PHONY:waves
waves: waveform.vcd
	@echo
	@echo "### WAVES ###"
	gtkwave waveform.vcd

waveform.vcd: ./obj_dir/V$(MODULE)
	@echo
	@echo "### SIMULATING ###"
	@./obj_dir/V$(MODULE)

./obj_dir/V$(MODULE): .stamp.verilate
	@echo
	@echo "### BUILDING SIM ###"
	make -C obj_dir -f V$(MODULE).mk V$(MODULE)

.stamp.verilate: $(MODULE).sv tb_$(MODULE).cpp
	@echo
	@echo "### VERILATING ###"
	verilator -Wall --trace -cc $(MODULE).sv --exe tb_$(MODULE).cpp
	@touch .stamp.verilate

.PHONY:lint
lint: $(MODULE).sv
	verilator --lint-only $(MODULE).sv

.PHONY: clean
clean:
	rm -rf .stamp.*;
	rm -rf ./obj_dir
	rm -rf waveform.vcd

快捷方式:保存之后,可以用 make sim快速仿真,make wave打开GTKWave,用make verilate用Verilator,用make build来 build the verilated sources,make lint能快速检查源代码,make clean能快速清楚在build过冲中产生的垃圾文件

### 02 随机初始值  
Verilator仿真只存在两种状态(0/1),意味着如果没有初值**默认置0**.
```bash
int main(int argc, char** argv, char** env) {
    Verilated::commandArgs(argc, argv); //随机初始值
    Valu *dut = new Valu;
<...>

再用 –x-assign unique and –x-initial unique来更新命令,具体实现:(应改为makefile第31行)

verilator -Wall --trace --x-assign unique --x-initial unique -cc $(MODULE).sv --exe tb_$(MODULE).cpp

最后,为了运行时信号初始化技术设置为随机,将+verilator+rand+reset+2传递给仿真执行,那么则需要在makefile中将Line21改为 @./obj_dir/V$(MODULE) +verilator+rand+reset+2 如图所示,第21行
利用make clean及make waves即可看到波形图(随机值)
在这里插入图片描述

03 DUT reset

为了复位dut及输入信号,在testbench中更新主循环如下

while (sim_time < MAX_SIM_TIME) {
    dut->rst = 0;//添加第2行以在后续循环迭代中将计数器重置为0
    if(sim_time > 1 && sim_time < 5)     //随意选择了reset信号在时钟边缘3和5之间,可自行更改
  {     dut->rst = 1;  //reset设置为高,后面几行都将输入设置为0
        dut->a_in = 0;
        dut->b_in = 0;
        dut->op_in = 0;
        dut->in_valid = 0;
    }
 //此后面不作修改,增加时间计数器
    dut->clk ^= 1;
    dut->eval();
    m_trace->dump(sim_time);
    sim_time++;
}

如图所示,reset信号已成功产生
在这里插入图片描述
为了让循环看的更清楚,将其在main()外单独分离成函数

void dut_reset (Valu *dut, vluint64_t &sim_time){
    dut->rst = 0;
    if(sim_time >= 3 && sim_time < 6){
        dut->rst = 1;
        dut->a_in = 0;
        dut->b_in = 0;
        dut->op_in = 0;
        dut->in_valid = 0;
    }
}

然后在主循环中添加 dut_reset调用

while (sim_time < MAX_SIM_TIME) {
   dut_reset(dut, sim_time);
    dut->clk ^= 1;
    dut->eval();
    m_trace->dump(sim_time);
    sim_time++;
}

04 基本验证

在主循环中有以下内容:

while (sim_time < MAX_SIM_TIME) {
    dut_reset(dut, sim_time);

    dut->clk ^= 1;
    dut->eval();
    m_trace->dump(sim_time);
    sim_time++;
}

现在,如果我们正在模拟Verilog/SystemVerilog测试台作为dut而不是我们的alu模块,我们可以为Verilated::gotFinish()添加一个检查,如果它被设置为true则停止模拟。

05 时钟沿计数

  1. 先创建一个计数上升沿的新变量,和sim_time同类型
    vluint64_t sim_time = 0;
    vluint64_t posedge_cnt = 0;
  1. 通过增加一个上升沿计数器来修改边沿生成代码
dut->clk ^= 1;            // Invert clock
dut->eval();              // Evaluate dut on the current edge
if(dut->clk == 1){
    posedge_cnt++;        // Increment posedge counter if clk is 1
}
m_trace->dump(sim_time);  // Dump to waveform.vcd
sim_time++;               // Advance simulation time 推进仿真时间

Line3-5 代码和Verilog以下表示相同:

initial posedge_cnt <= '0;
always_ff @ (posedge clk, posedge rst) begin
    posedge_cnt <= posedge_cnt + 1'b1;
end

06 初始DUT激励及检查

在这里插入图片描述
首先检查输入信号传输到输出(忽略a,b,op_in,data output)
我们以及知道有两个寄存器阶段,具体如下:

always_ff @ (posedge clk) begin
    in_valid_r <= in_valid;
    out_valid <= out_valid_r;
end

因此,如果我们在第5个正时钟边缘上对in_valid应用1,那么在两个时钟周期后,我们应该看到out_valid上的值为1,即第7个上升沿

while (sim_time < MAX_SIM_TIME) {
    dut_reset(dut, sim_time);

    dut->clk ^= 1;
    dut->eval();

   / dut->in_valid = 0;
    if (dut->clk == 1){
        posedge_cnt++;
        if (posedge_cnt == 5){
            dut->in_valid = 1;       // assert in_valid on 5th cc
        }
        if (posedge_cnt == 7){
            if (dut->out_valid != 1) // check in_valid on 7th cc
                std::cout << "ERROR!" << std::endl;
        }
    }       /

    m_trace->dump(sim_time);
    sim_time++;
}

以上/ …/中(标记代码)同以下表达相同

always_comb begin
    in_valid = 0;
    if (posedge_cnt == 5)
        in_valid = 1;

    if (posedge_cnt == 7)
        assert (out_valid == 1) else $error("ERROR!")
end

仿真结果:
在这里插入图片描述
这里的要点是确保你编写的刺激/检查代码遵循这个操作顺序:

  1. 将时钟设置为1,创建上升沿,然后在dumping and incrementing sim time之前设置输入/检查输出。
  2. 在while()循环内的下一个上升沿,之前设置的输入将在eval 传至设计中,然后在eval之后,输入应该被重置为它们的默认值。

07 类似信号检测器

将in_valid设置在第5个上升沿上,并检查out_valid是否为1,这肯定是有效的;但如果我们想在更多的时钟周期上检查有效性,就需要添加更多的检查。此外,我们没有检查out_valid是否为0,而它应该是0,这意味着我们的out_valid可能停留在1。因此,我们的验证代码可以通过编写一些c++代码来持续监视in_valid和out_valid,代码如下所示

#define VERIF_START_TIME 7 
//保证在reset前不会运行检查代码,防止出现错误检测
//如上图所示,rst在6ps时为0,所以当sim_time=7时即为检查开始时间
//检查代码很简单,只是在in_valid and out_valid之间的寄存器流水线进行建模
void check_out_valid(Valu *dut, vluint64_t &sim_time){
    static unsigned char in_valid = 0; //in valid from current cycle
    static unsigned char in_valid_d = 0; //delayed in_valid
    static unsigned char out_valid_exp = 0; //expected out_valid value

    if (sim_time >= VERIF_START_TIME) {
        // note the order!
        out_valid_exp = in_valid_d;
        in_valid_d = in_valid;
        in_valid = dut->in_valid;
        if (out_valid_exp != dut->out_valid) {
            std::cout << "ERROR: out_valid mismatch, "
                << "exp: " << (int)(out_valid_exp)
                << " recv: " << (int)(dut->out_valid)
                << " simtime: " << sim_time << std::endl;
        }
    }
}

//也可用以下代码替代
while (sim_time < MAX_SIM_TIME) {
    dut_reset(dut, sim_time);

    dut->clk ^= 1;
    dut->eval();

    if (dut->clk == 1){
        dut->in_valid = 0;
        posedge_cnt++;
        if (posedge_cnt == 5){
            dut->in_valid = 1;
        }
        check_out_valid(dut, sim_time);
    }

    m_trace->dump(sim_time);
    sim_time++;
}

随机数的生成

在结束Verilator指南系列的这一部分之前,让我们也快速将in_valid的单一赋值替换为随机设置为1或0的东西。
为此,我们可以加入C++头文件cstdlib

#include <cstdlib>

并在自定义的set_rnd_out_valid函数中使用伪随机数生成函数rand()来生成随机的1和0。

void set_rnd_out_valid(Valu *dut, vluint64_t &sim_time){
    if (sim_time >= VERIF_START_TIME) {
        dut->in_valid = rand() % 2; // generate values 0 and 1
    }
}

我们还需要通过调用srand来给随机数发生器播种,这可以直接放在主函数的开头。

int main(int argc, char** argv, char** env) {
    srand (time(NULL));
    Verilated::commandArgs(argc, argv);
    Valu *dut = new Valu;
    <...>

我们还应该将MAX_SIM_TIME增加到更大的值,比如300:

#define MAX_SIM_TIME 300

在执行make simmake waves之后,产生随机值如下:
在这里插入图片描述

Finished testbench

#include <stdlib.h>
#include <iostream>
#include <cstdlib>
#include <verilated.h>
#include <verilated_vcd_c.h>
#include "Valu.h"
#include "Valu___024unit.h"

#define MAX_SIM_TIME 300
#define VERIF_START_TIME 7
vluint64_t sim_time = 0;
vluint64_t posedge_cnt = 0;

void dut_reset (Valu *dut, vluint64_t &sim_time){
    dut->rst = 0;
    if(sim_time >= 3 && sim_time < 6){
        dut->rst = 1;
        dut->a_in = 0;
        dut->b_in = 0;
        dut->op_in = 0;
        dut->in_valid = 0;
    }
}

void check_out_valid(Valu *dut, vluint64_t &sim_time){
    static unsigned char in_valid = 0; //in valid from current cycle
    static unsigned char in_valid_d = 0; //delayed in_valid
    static unsigned char out_valid_exp = 0; //expected out_valid value

    if (sim_time >= VERIF_START_TIME) {
        out_valid_exp = in_valid_d;
        in_valid_d = in_valid;
        in_valid = dut->in_valid;
        if (out_valid_exp != dut->out_valid) {
            std::cout << "ERROR: out_valid mismatch, "
                << "exp: " << (int)(out_valid_exp)
                << " recv: " << (int)(dut->out_valid)
                << " simtime: " << sim_time << std::endl;
        }
    }
}

void set_rnd_out_valid(Valu *dut, vluint64_t &sim_time){
    if (sim_time >= VERIF_START_TIME) {
        dut->in_valid = rand() % 2;
    }
}

int main(int argc, char** argv, char** env) {
    srand (time(NULL));
    Verilated::commandArgs(argc, argv);
    Valu *dut = new Valu;

    Verilated::traceEverOn(true);
    VerilatedVcdC *m_trace = new VerilatedVcdC;
    dut->trace(m_trace, 5);
    m_trace->open("waveform.vcd");

    while (sim_time < MAX_SIM_TIME) {
        dut_reset(dut, sim_time);

        dut->clk ^= 1;
        dut->eval();

        if (dut->clk == 1){
            dut->in_valid = 0;
            posedge_cnt++;
            set_rnd_out_valid(dut, sim_time);
            check_out_valid(dut, sim_time);
        }

        m_trace->dump(sim_time);
        sim_time++;
    }

    m_trace->close();
    delete dut;
    exit(EXIT_SUCCESS);
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值