一、简介
上一篇文章已经讲过了verilator官方提供的C++示例。
链接:一生一芯 预学习阶段 搭建verilator仿真环境 以及 运行官方示例-CSDN博客
在一生一芯学习的教程文档中也有一个双控开关的示例,但是提供的代码需要我们自己进行修改才能完善。源代码如下,这是不能正常仿真的:
因此接下来分两步来完成这个示例:
- 1、学习verilator,以及它的操作(会有一些拓展)
(本部分借用verilator官方例程学习,链接:Example C++ Execution — Verilator Devel 5.027 documentation);
- 2、完成该示例。
二、详解
1、verilator的学习
1)、Verilator 基本介绍
Verilator 本质上是一个 Verilog / SystemVerilog 仿真器。 它速度快,免费,开源。但不能直接替代 Modelsim、Questa Sim、Synopsys VCS、Vivado Xsim 和其他基于事件的仿真器。 Verilator 是一种基于周期的仿真器,这意味着它不会评估单时钟周期内的时间,也不会仿真精确的电路时序, 电路状态通常在每个时钟周期评估一次,因此无法观察到周期内的任何故障,也不支持定时信号延迟。
Verilator 要求将 C++ 测试平台编译成本地系统二进制文件。
但是,我们不能将我们的 Verilog / System Verilog 模块原封不动地加入 C++ 测试平台,我们首先需要使用 Verilator 将 Verilog / System Verilog 代码转换为 C++,或者说 "Verilate"(验证)它。
也就是说Verilator 是把 .v 的文件编译成C++文件,再进行验证。
执行如下代码后就将所有编译后的文件加入到 obj_dir文件中:
verilator --cc example.v
会生成很多类似文件:
$ ls -l obj_dir/
Vexample___024unit.cpp Vexample___024unit__Slow.cpp Vexample.cpp Vexample.mk
Vexample__Syms.cpp Vexample__ver.d Vexample___024unit.h Vexample_classes.mk
Vexample.h Vexample__Slow.cpp Vexample__Syms.h Vexample__verFiles.dat
- .mk 文件将与 Make 一起用于构建我们的仿真可执行文件;
- .h 和 .cpp 文件包含我们的 C++ 头文件和实现源代码;
- Vexample.h 这是主设计头文件,包含转换后的 "example "类定义,将在 C++ 测试平台中将其 "实例化 "为 DUT;
- Vexample___024unit.h 这是 "example "类的内部头文件,其中包含 operation_t 类型定义;
到此DUT模块算是完成。
接下来看testbench(C++)
官方说明文档给出的C++示例:
#include "Vour.h"
#include "verilated.h"
int main(int argc, char** argv) {
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vour* top = new Vour{contextp};
while (!contextp->gotFinish()) { top->eval(); }
delete top;
delete contextp;
return 0;
}
先看头文件:
我们需要include Verilator 安装时附带的 <verilated.h> 以访问常用的 Verilator 例程,
另外还有 #include <verilated_vcd_c.h>,将波形写入 VCD文件;
"Vour.h "包含了我们的 Verilated ALU 模块的顶层类;
还可以# include "Valu___024unit.h", 包含了我们的 typedef 枚举的 Verilated 版本。
头文件内容到此。
接下来是比较重要的主函数:
# 是这一部分
int main(int argc, char** argv) {
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vour* top = new Vour{contextp};
/*<......>*/
delete top;
delete contextp;
return 0;
}
这一部分
Vour* top = new Vour{contextp};
这样的代码,这个代码就类似于SV里面的 example dut_1 (.*); 也就是将模块例化进去。
其中 dut 就是例化的名字。
这一行代码
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
用 VerilatedContext 创建一个 contextp 的对象,contextp 的对象里面有commandArgs方法;里面有argc和argv两个参数。
这一行:
Vour* top = new Vour{contextp};
用 Vour 的类创建一个 top 的对象并new 一下,Vour应该是来源于 "Vout.h" 的头文件。
最后的几行:
delete top;
delete contextp;
return 0;
就是清空内存。
2)、示例完成代码
DUT模块代码:
module switch(
input a,
input b,
output f
);
assign f = a ^ b;
endmodule
sim.cpp文件代码(我做了两种写法):
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Vswitch.h"
int main(int argc, char** argv){
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vswitch* top = new Vswitch{contextp};
while (!contextp->gotFinish()){
int a = rand() & 1;
int b = rand() & 1;
top->a = a;
top->b = b;
top->eval();
printf("a = %d, b = %d, f = %d\n", a, b, top->f);
assert(top->f == (a ^ b));
}
delete top;
delete contextp;
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Vswitch.h"
int main(int argc, char** argv) {
Verilated::commandArgs(argc,argv);
Vswitch *top = new Vswitch("top");
while (!Verilated::gotFinish()){
int a = rand() & 1;
int b = rand() & 1;
top->a = a;
top->b = b;
top->eval();
printf("a = %d, b = %d, f = %d\n", a, b, top->f);
assert(top->f == (a ^ b));
}
delete top;
return 0;
}
我认真为这两种区别就是argc和argv这两个参数能不能单独访问。
同时argc和argv这两个参数的值应该就来自于终端中输入的参数。
两个模块写完之后执行如下代码:
$ verilator --cc --exe --build -j 0 -Wall sim.cpp switch.v
$ obj_dir/Vswitch
终端中就会有结果不断输出,如图:
因为不会停止,所以要按 <Ctrl + c> 。
关于不会停止的问题,应该是 !contextp->gotFinish() 的问题;
gotFinish() 是 VerilatedContext 类的一个成员函数,它用于检测仿真是否收到了结束信号(如 $finish 或 $stop 语句)
因为DUT中没有相关可以暂停的语句,因此我做了如下修改:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Vswitch.h"
int main(int argc, char** argv){
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vswitch* top = new Vswitch{contextp};
int counter = 0;
while (!contextp->gotFinish()){
int a = rand() & 1;
int b = rand() & 1;
top->a = a;
top->b = b;
top->eval();
printf("a = %d, b = %d, f = %d\n", a, b, top->f);
assert(top->f == (a ^ b));
counter++;
}
delete top;
delete contextp;
return 0;
}
实现了值计算十次后暂停,结果如下: