在用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
利用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 时钟沿计数
- 先创建一个计数上升沿的新变量,和sim_time同类型
vluint64_t sim_time = 0;
vluint64_t posedge_cnt = 0;
- 通过增加一个上升沿计数器来修改边沿生成代码
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,创建上升沿,然后在dumping and incrementing sim time之前设置输入/检查输出。
- 在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 sim
和make 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);
}