最近在学习Verilator背后的原理,本文对于Verilator的基本功能做了简要的叙述,并简要讲解了hello world的运行过程。也算是学习之中的一点笔记了!
更详细的内容,一定还是要看Verilator的官方User Guide啊!https://verilator.org/guide/latest/verilating.html
Verilator总体介绍
Verilator将Verilog等HDL设计转化为可执行的C++或SystemC的model。与传统的HDL仿真器不同,Verilator更像是一个编译器。
1、与GCC类似,Verilator需要用户指定编译时的各种配置参数。对于若干SV代码文件,Verilator对其进行静态检查,之后有选择地在代码中加入coverage收集或waveform tracing等instrument。最后将SV的DUT编译为一个C++或SystemC源代码级别的软件多线程模型。这个软件模型由若干.cpp .h文件组成。我们管这个编译的过程叫Verilating,管这个DUT的软件模型叫做 “A Verilated Model”
2、如果想对于DUT转化来的Verilated Model进行仿真,用户需要写少量的C++ wrapper files。在wrapper中,包含整体仿真程序的main函数入口。DUT的Verilated Model在仿真程序中是一个实例化的C++类。wrapper在这里的作用相当于为已经软件化的DUT提供一个实际的运行平台。
(如下面代码中,ibex_simple_system就是待测SoC系统经Verilating后得到的C++ 类)
3、最终,用户编写的C++ wrapper(几个手写的C++文件)、由DUT转化来的Verilated Model(一大堆自动生成的C++文件)、和由Verilator提供的若干库,一起经过编译链接,最终形成一个可执行文件。
4、运行这一可执行文件会启动实际的仿真。
5、可执行文件(仿真)结束后,会产生覆盖率分析,波形文件等数据,用户可以对此进行后续的分析。
Verilator Hello World
下面我们用Verilator提供的hello world例子来解释上面的过程。
下面就是我们的DUT。module中只有一个initial块,输出hello world之后结束仿真。
//our.v
module our;
initial begin
$display("Hello World");
$finish;
end
endmodule
下面是我们的wrapper.
Vour.h是由Verilator对我们的our module编译之后产生的文件。Vour也就是DUT对应的软件model。
// sim_main.cpp
#include "Vour.h"
#include "verilated.h"
int main(int argc, char** argv, char** env) {
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vour* top = new Vour{contextp};
while (!contextp->gotFinish()) { top->eval(); }
delete top;
delete contextp;
return 0;
}
运行如下命令,该命令执行了这些功能:对our.v执行verilating生成C++模型,将wrapper files、Vour的C++模型、以及Verilator提供的库,一起编译链接生成一个可执行文件Vour。
verilator --cc --exe --build -j 0 -Wall sim_main.cpp our.v
命令运行完会生成如下的文件。这里面的Vour是最终仿真程序的可执行文件。
运行这一可执行文件会得到如下结果。
这是因为module中只有一个initial块。在这个块中,display了一个hello word,之后就调用finish结束仿真了。(display和finish都是不可综合,但可以仿真的Verilog系统函数)。
对应到sim_main.cpp中的行为,主程序先启动一个context类,之后读取command args以配置相关环境(这里我们命令中没有更多的args)。在环境准备完成后, 实例化Vour类。
当eval()函数被调用时,Verilator会寻找时钟信号的变化,之后对相关的always块中的逻辑进行状态更新和计算。由于这里只有一个initial块,我们也没有设置时钟信号,第一次调用eval时只会执行到initial块中的内容,在display之后发出了finish信号。结束了这次eval之后,contextp->gotFinish()检测到finish信号的触发,于是终止循环。结束整个仿真程序。