和我们平时熟悉的xsim
仿真器不同,Verilator是一款高性能的仿真器,其高性能的秘诀就在于将Verilog代码编译成C++模型进行执行,这样可以达到多线程仿真的效果。
如果想要理解使用模拟器(例如NEMU)进行差分测试的原理,就必须理解Verilator仿真的机制和流程。
本文总结了理解差分测试程序需要的Verilator预备知识。
Verilator仿真全流程
- 类似于GCC的,我们调用
verilator
可执行文件,后接相关的Verilog源文件,verilator
将Verilog源文件编译为C++模型或者System C模型。在手册中,他们把这个叫做verilating
,输出的模型叫做verilated
的模型。 - Verilate完成之后,并不是万事大吉,用户需要编写一个C++的
wrapper
模块,其中包含main()
函数,实例化了编译好的模型。 - 使用C++编译器,将Verilator编译好的模型、用户编写的顶层
wrapper
和Verilator提供的库函数编译成可执行的仿真文件。 - 执行可执行文件,完成仿真。
一个简单的例子
our.v
:
1 | module our; |
sim_main.cpp
:
1 |
|
使用相关的命令进行编译:
1 | verilator -Wall --cc our.v --exe --build sim_main.cpp |
仿真过程
当我们使用C++模型进行仿真的时候,用户编写的顶层函数必须调用eval()
或者eval_step()
和eval_end_step()
。
当我们在C++层面,仅仅实例化一个模型的时候,只需要调用designPtr->eval()
,事实上,eval()
和eval_step()+eval_end_step()
等效。
当每次eval()
被调用的时候,就会执行一次always @(posedge clk)
语句,计算相应的结果,然后计算组合逻辑。
和C++的交互
对于Verilator而言,会对顶层测试模块创建一个Vxxx.h
和Vxxx.c
文件,还有其他的模块文件,此处我们并不关注。
在编译的过程中,还会有Vxxx.mk
文件,目标文件是Vxxx_ALL.a
文件,会和用户编写的C++主函数中的循环链接,并创建出仿真的可执行文件。
我们来看以下的代码:
1 |
|
由上面代码可见,仿真时如果需要访问模块的顶层信号,可以使用top->signal
的形式进行访问。
DPI(直接编程接口)
一个简单的例子
如果我们想在Verilog中调用C语言编写的函数,可以在our.v
的头部声明:
1 | import "DPI-C" function int add (input int a, input int b); |
在Verilator编译之后,将会创建一个Vour__Dpi.h
文件,里面有以下的extern声明:
1 | extern int add (int a, int b); |
此后,在其他文件中实现这个add
函数,记住,需要包含两个头文件:
1 |
|
DPI系统任务/函数
Verilator还支持将外部的C函数作为系统函数,需要使用$
作为前缀,但注意,$
前缀需要被转义:
1 | export "DPI-C" function integer \$myRand; |
同样地,我们也可以将Verilog中的task导出为C++函数:
1 | export "DPI-C" task publicSetBool; |
在”Verilate”之后,Verilator就会创建一个Vour__Dpi.h
文件,其中有相应函数的原型:
1 | extern void publicSetBool(svBit in_bool); |
此时,我们可以在sc_main.cpp
文件中条用相关的函数了。
1 |
|
或者以如下的形式显式调用:
1 |
|
关于更多DPI的使用方法,可以参考Verilator的官方手册第15章:https://www.veripool.org/ftp/verilator_doc.pdf