Verilator简介与使用

Verilator简介与使用_第1张图片
Welcome to Verilator, the fastest Verilog/System Verilog simulator.

  • Accepts Verilog or System Verilog
  • Performs lint code-quality checks
  • Compiles into multithreaded C++, or System C
  • Creates XML to front-end your own tools

本文将对Verilator进行简单的介绍与使用演示,Verilator是一款开源的支持Verilog和System Verilog仿真工具,它支持代码质量检查等功能,能够将给定的电路设计(由Verilog或System Verilog编写)编译成(或者说翻译成)C++或者System C的库等中间文件,最后再编写testbench(在Verilator中叫做wrapper file),去调用前面生成的中间文件,然后统一由C编译器编译执行,来完成仿真。

1. Hello World

安装本文不进行具体介绍,参考以下链接即可,若使用apt install安装则要注意verilator的版本:

Verilator安装手册

如下是安装成功

Verilator简介与使用_第2张图片

安装成功之后,我们来进行简单的演示,打印一个Hello world,直接使用verilator给定的工程即可,若上文使用的是apt install方法安装,则git clone:

git clone https://github.com/verilator/verilator
cd verilator/examples/make_hello_c

我们可以看到makefile,sim_main.cpp和top.v
先看看top.v的代码

module top;
initial begin
$display("Hello World!");
$finish;
end
endmodule

一个top模块,使用了系统函数$display,打印hello world

我们在前文讲到过,verilator的工作流程是将电路(top.v)翻译成一个库和一系列中间文件,再由wrapper file(sim_main.cpp)调用这个些中间文件,使用C编译器编译执行,打印结果完成仿真

接下来再看看sim_main.cpp

#include 
// Include model header, generated from Verilating "top.v"
#include "Vtop.h"
int main(int argc, char** argv) {
// See a similar example walkthrough in the verilator manpage.
// This is intended to be a minimal example. Before copying this to start a
// real project, it is better to start with a more complete example,
// e.g. examples/c_tracing.
// Construct a VerilatedContext to hold simulation time, etc.
VerilatedContext* contextp = new VerilatedContext;
// Pass arguments so Verilated code can see them, e.g. $value$plusargs
// This needs to be called before you create any model
contextp->commandArgs(argc, argv);
// Construct the Verilated model, from Vtop.h generated from Verilating "top.v"
Vtop* top = new Vtop{contextp};
// Simulate until $finish
while (!contextp->gotFinish()) {
// Evaluate model
top->eval();
}
// Final model cleanup
top->final();
// Destroy model
delete top;
// Return good completion status
return 0;
}

注释写的很清楚,我们只介绍一下重点,首先Include “Vtop.h”,这个Vtop就是由top.v翻译而来,接下来new一个对象,这个类可能有很多个变量,后文会使用,接着进入一个while循环,直到遇到结束信号。

接下来看看makefile
关键的两句,第一步是进行翻译,第二步是执行

 $(VERILATOR) -cc --exe --build -j top.v sim_main.cpp
 obj_dir/Vtop

前面谈到过,verilator的wrapper file支持C++和System C++,-cc表示翻译成C++库,–exe表示生成可执行文件,后面就必须指定wrapper file,-j是指定线程

我们make一下,看到Hello World!,大功告成

Verilator简介与使用_第3张图片

现在多了一个"obj_dir","obj_dir"中存放的就是我们前面谈到的生成的库,当然,这个生成位置可由 --Mdir指定

ls
Vtop      Vtop__ALL.a    Vtop__Syms.cpp      Vtop___024root__Slow.cpp  sim_main.d
Vtop.cpp  Vtop__ALL.cpp  Vtop__Syms.h        Vtop__ver.d               sim_main.o
Vtop.h    Vtop__ALL.d    Vtop___024root.cpp  Vtop__verFiles.dat        verilated.d
Vtop.mk   Vtop__ALL.o    Vtop___024root.h    Vtop_classes.mk           verilated.o

其中包括一系列的库,cpp和各种中间文件,以及可执行文件Vtop
在上面的makefile中,使用了选项–build,当然我们也可以不直接build

verilator -cc -exe sim_main.cpp top.v
make -j -C obj_dir -f Vtop.mk Vtop
./obj_dir/Vtop

2. Verilator工具介绍

前文对于Verilator的工作流程进行了演示,现在再进行详细补充。

Verilator的作用是将某些Verilog或System Verilog代码翻译成可编译的C++或System C代码,或者库,之后再编写合适的驱动代码也就是sim_main.cpp,一同使用C编译器进行编译,由此得到可执行的程序,来对Verilog或System Verilog代码进行仿真,得到波形图等仿真结果。

其详细的执行过程如下:

  1. ​Verilator通过读取指定的 Verilog 或 System Verilog 代码、对其执行 lint 静态代码检查以及可选地插入断言检查和覆盖分析点。它可以输出单线程或多线程的 .cpp 和 .h 文件,即“Verilated(经过验证的)”代码。

  2. ​用户需要编写的一个C++ / System C Wrapper file 封装文件,这个文件会定义main()函数,它会将“Verilated”模型实例化为C++/System C对象。

  3. ​用户的Wrapper file、Verilator创建的文件、Verilator提供的“运行时的库”和System C库,这些 C++ / System C 文件之后会由 C++ 编译器 (gcc / clang / MSVC++) 统一进行编译。生成的可执行文件来执行设计仿真。Verilator 还支持将其生成的库(可选加密)链接到其他模拟器。

  4. 生成的可执行文件将在“模拟运行”期间执行实际的模拟。还可以生成可查看的设计波形轨迹。

3. Verilator性能表现


Verilator 并非简单地将 Verilog HDL 转换为 C++ 或 System C。Verilator会将代码编译成一个优化更快的和可选的线程分区模型,该模型又包装在 C++/ System C 模块中。结果就是一个编译好的 Verilog 模型,即使在单线程上的执行速度也比独立的System C快10倍以上,在单线程上的执行速度比解释过的Verilog模拟器(如Icarus Verilog)快100倍左右。多线程可能会带来2-10倍的加速(比解释的模拟器快200-1000倍)。 Verilator 通常具有与闭源 Verilog 模拟器(Carbon Design Systems Carbonator、Modelsim、Cadence Incisive/NC-Verilog、Synopsys VCS、VTOC 和 Pragmatic Cver /CVC)相似或更好的性能。

4. 翻译代码的结构

​我们先回到编译之前:

rm -rf obj_dir
verilator -cc -exe sim_main.cpp top.v
ls obj_dir

Verilator简介与使用_第4张图片

Verilator根据top.v输出了一个Vtop.h的头文件,该文件定义了一个名为{Vtop}的类,该类表示用户应该实例化的生成模型。这个模型类定义了验证模型的接口。

​Verilator还会创建一个Vtop.cpp文件,以及内部的 .h 和 .cpp 文件。

​Verilator的输出将包含一个Vtop.mk文件,该文件可以与Make一起使用来构建一个Vtop__ALL .a静态库文件,它包含所有必需对象的库。

与sim_main.cpp编译之后,则会多出一些sim_main的中间文件和最后的可执行文件Vtop

make -j -C obj_dir -f Vtop.mk Vtop

Verilator简介与使用_第5张图片

5. C++输出模式

上文我们将top.v电路翻译为C++模型,在 C++ 输出模式 (–cc) 下,Verilator 生成的模型是一个 C++ 类。我们要为模型编写一个 C++ wrapper file ,来实例化模型类,并与 Verilator翻译后的模型链接。以下给出一个sim_main.cpp的例子
https://github.com/verilator/verilator/blob/master/examples/make_tracing_c/sim_main.cpp

#include 
#include 
#include "Vtop.h"
double sc_time_stamp() { return 0; }
int main(int argc, char** argv) {
    if (false && argc && argv) {}
    Verilated::mkdir("logs");
    const std::unique_ptr contextp{new VerilatedContext};
    contextp->debug(0);
    contextp->randReset(2);
    contextp->traceEverOn(true);
    contextp->commandArgs(argc, argv);
    const std::unique_ptr top{new Vtop{contextp.get(), "TOP"}};
    top->reset_l = !0;
    top->clk = 0;
    top->in_small = 1;
    top->in_quad = 0x1234;
    top->in_wide[0] = 0x11111111;
    top->in_wide[1] = 0x22222222;
    top->in_wide[2] = 0x3;
    while (!contextp->gotFinish()) {
        contextp->timeInc(1);  
        top->clk = !top->clk;
        if (!top->clk) {
            if (contextp->time() > 1 && contextp->time() < 10) {
                top->reset_l = !1;  // Assert reset
            } else {
                top->reset_l = !0;  // Deassert reset
            }
            top->in_quad += 0x12;
        }
        top->eval();
        VL_PRINTF("[%" PRId64 "] clk=%x rstl=%x iquad=%" PRIx64 " -> oquad=%" PRIx64
                  " owide=%x_%08x_%08x\n",
                  contextp->time(), top->clk, top->reset_l, top->in_quad, top->out_quad,
                  top->out_wide[2], top->out_wide[1], top->out_wide[0]);
    }
    top->final();
    Verilated::mkdir("logs");
    contextp->coveragep()->write("logs/coverage.dat");
    return 0;
}

我们在main函数中实例化top对象,通过将top的成员变量reset_l赋值为1等效于我们在testbench中的赋值,即电路模型顶层的 IO 信号会作为Vtop类的成员被读取和写入。在每一圈循环中,我们调用timeInc()将time++,等效于时钟周期+1,因此我们可以在这个while循环中完成对一系列变量的赋值和打印,不止于in_quad和reset_l,这相比于verilog testbench更灵活。调用 eval() 方法来评估模型。当模拟完成时,调用 final() 方法,接着进行Coverage覆盖率分析,write生成.dat文件。生成源文件中有注释,可仔细阅读。

6. System C输出模式

前文我们都在使用C++模式,因为它更方便,若想使用System C模式则需要安装System C的库lib,这个库中包含了时钟,模块,引脚等一系列概念,就是扩展后的C++,System C也是RTL级HDL,和Verilog很相似。下面给出参考

https://github.com/verilator/verilator/blob/master/examples/make_hello_sc/sc_main.cpp

#include 
#include 
#include "Vtop.h"

int sc_main(int argc, char* argv[]) {
    Vtop* top = new Vtop{"top"};
    Verilated::commandArgs(argc, argv);
    sc_start(1, SC_NS);
    while (!Verilated::gotFinish()) {
        sc_start(1, SC_NS);
    }
    top->final();
    return 0;
}

System C的wrapper file引用了systemc.h,例如sc_start则是systemc 库函数,代码不难理解,和C++类似。

在 System C 输出模式 (–sc) 中,Verilator 生成的模型类是 System C SC_MODULE。该模块将作为实例直接附加到 System C 网表中。 SC_MODULE 获得与 Verilog 模块相同的引脚分配,具有以下类型转换: 单个位的引脚变为 bool。 2-32 位宽的引脚变为 uint32_t。 33-64 位宽的引脚变为 sc_bv 或 vluint64_t,具体取决于 --no-pins64 选项。更宽的引脚成为 sc_bv 的。 模型内部,包括较低级别的子模块不是纯 System C 代码。这是一个特性,因为在任何地方使用 System C 引脚互连方案都会将性能降低一个数量级。

7. 运行实例

前文对于Verilator的理论进行了详细介绍,接下来演示一个包含时序逻辑和组合逻辑的的边沿检测电路仿真

a信号的上升沿给出指示信号rise,当a信号出现下降沿时给出指示信号down

7.1 待测电路

top.v

`timescale 1ns/1ns
module edge_detect(
	input clk,
	input rst_n,
	input a,
	
	output reg rise,
	output reg down
);
	
	reg a_dely;
	always @(posedge clk or posedge rst_n)
	begin
		if(~rst_n)
		begin
			rise<=0;
			down<=0;
			a_dely<=0;
		end
		else
		begin
			a_dely<=a;
			if(~a_dely&&a)
			begin
				rise<=1;
				down<=0;
			end
			else if(a_dely&~a)
			begin
				down<=1;
				rise<=0;
			end
			else
			begin
				rise<=0;
				down<=0;
			end
		end
	end
endmodule

7.2 C++模式的Wrapper file

下面我们给出C++输出模式的sim_main.cpp

#include 
#include 
#include "Vtop.h"
#include "verilated_vcd_c.h"

double sc_time_stamp() { return 0; }
int main(int argc, char **argv)
{
    if (false && argc && argv){}
    const std::unique_ptr contextp{new VerilatedContext};
    contextp->debug(0);
    contextp->randReset(2);
    contextp->traceEverOn(true);
    contextp->commandArgs(argc, argv);
    const std::unique_ptr top{new Vtop{contextp.get(), "TOP"}};
    VerilatedVcdC *tfp = new VerilatedVcdC;
    top->trace(tfp, 0);
    tfp->open("wave.vcd"); // 设置输出的文件wave.vcd
    top->rst_n = 0;
    top->clk = 0;
    top->a = 0;
    while (!contextp->gotFinish())
    {
        contextp->timeInc(1);
        top->clk = !top->clk;
        if (!top->clk)
        {
            if (contextp->time() > 1 && contextp->time() < 10)
            {
                top->rst_n = 0; // Assert reset
            }
            else
            {
                top->rst_n = 1; // Deassert reset
            }
            if (contextp->time() > 15 && contextp->time() < 20)
            {
                top->a = 0;
            }
            if (contextp->time() >= 20 && contextp->time() < 30)
            {
                top->a = 1;
            }
            if (contextp->time() >= 30 && contextp->time() < 40)
            {
                top->a = 0;
            }
            if (contextp->time() >= 60)
            {
                break;
            }
        }
        top->eval();
        tfp->dump(contextp->time()); // dump wave
        VL_PRINTF("[%" PRId64 "] clk=%x rst_n=%x a=%x rise=%x down=%x \n", contextp->time(), top->clk, top->rst_n, top->a, top->rise, top->down);
    }
    top->final();
    tfp->close();
    return 0;
}

在这个wrapper file中,我们增加调用了verilated_vcd_c.h,将这些IO的状态导出为波形vcd,供波形工具查看,

verilator -Wall --trace -cc -exe top.v sim_main.cpp
make -j -C obj_dir -f Vtop.mk
./obj_dir/Vtop

我们将运行结果复制下来,注意在本wrapper file中,clk为1表示这个时刻由0->1,根据上面的sim_main.cpp我们不难理解,在while循环中,运行完contextp->timeInc(1)函数后,time+1,接着top->clk = !top->clk,即clk由默认值变成1,表示这个时刻由0->1,即为上升沿

下面并没有打印contextp->time()为0的时刻,若打印,应该是

[0] clk=0 rst_n=0 a=0 rise=0 down=0 //因为在循环开始前,clk已经赋初值0

往下

[1] clk=1 rst_n=0 a=0 rise=0 down=0
[2] clk=0 rst_n=0 a=0 rise=0 down=0
[3] clk=1 rst_n=0 a=0 rise=0 down=0
[4] clk=0 rst_n=0 a=0 rise=0 down=0
[5] clk=1 rst_n=0 a=0 rise=0 down=0
[6] clk=0 rst_n=0 a=0 rise=0 down=0
[7] clk=1 rst_n=0 a=0 rise=0 down=0
[8] clk=0 rst_n=0 a=0 rise=0 down=0
[9] clk=1 rst_n=0 a=0 rise=0 down=0
[10] clk=0 rst_n=1 a=0 rise=0 down=0
[11] clk=1 rst_n=1 a=0 rise=0 down=0
[12] clk=0 rst_n=1 a=0 rise=0 down=0
[13] clk=1 rst_n=1 a=0 rise=0 down=0
[14] clk=0 rst_n=1 a=0 rise=0 down=0
[15] clk=1 rst_n=1 a=0 rise=0 down=0
[16] clk=0 rst_n=1 a=0 rise=0 down=0
[17] clk=1 rst_n=1 a=0 rise=0 down=0
[18] clk=0 rst_n=1 a=0 rise=0 down=0
[19] clk=1 rst_n=1 a=0 rise=0 down=0
[20] clk=0 rst_n=1 a=1 rise=0 down=0
[21] clk=1 rst_n=1 a=1 rise=1 down=0
[22] clk=0 rst_n=1 a=1 rise=1 down=0
[23] clk=1 rst_n=1 a=1 rise=0 down=0
[24] clk=0 rst_n=1 a=1 rise=0 down=0
[25] clk=1 rst_n=1 a=1 rise=0 down=0
[26] clk=0 rst_n=1 a=1 rise=0 down=0
[27] clk=1 rst_n=1 a=1 rise=0 down=0
[28] clk=0 rst_n=1 a=1 rise=0 down=0
[29] clk=1 rst_n=1 a=1 rise=0 down=0
[30] clk=0 rst_n=1 a=0 rise=0 down=0
[31] clk=1 rst_n=1 a=0 rise=0 down=1
[32] clk=0 rst_n=1 a=0 rise=0 down=1
[33] clk=1 rst_n=1 a=0 rise=0 down=0
[34] clk=0 rst_n=1 a=0 rise=0 down=0
[35] clk=1 rst_n=1 a=0 rise=0 down=0
[36] clk=0 rst_n=1 a=0 rise=0 down=0
[37] clk=1 rst_n=1 a=0 rise=0 down=0
[38] clk=0 rst_n=1 a=0 rise=0 down=0
[39] clk=1 rst_n=1 a=0 rise=0 down=0
[40] clk=0 rst_n=1 a=0 rise=0 down=0
[41] clk=1 rst_n=1 a=0 rise=0 down=0
[42] clk=0 rst_n=1 a=0 rise=0 down=0
[43] clk=1 rst_n=1 a=0 rise=0 down=0
[44] clk=0 rst_n=1 a=0 rise=0 down=0
[45] clk=1 rst_n=1 a=0 rise=0 down=0
[46] clk=0 rst_n=1 a=0 rise=0 down=0
[47] clk=1 rst_n=1 a=0 rise=0 down=0
[48] clk=0 rst_n=1 a=0 rise=0 down=0
[49] clk=1 rst_n=1 a=0 rise=0 down=0
[50] clk=0 rst_n=1 a=0 rise=0 down=0
[51] clk=1 rst_n=1 a=0 rise=0 down=0
[52] clk=0 rst_n=1 a=0 rise=0 down=0
[53] clk=1 rst_n=1 a=0 rise=0 down=0
[54] clk=0 rst_n=1 a=0 rise=0 down=0
[55] clk=1 rst_n=1 a=0 rise=0 down=0
[56] clk=0 rst_n=1 a=0 rise=0 down=0
[57] clk=1 rst_n=1 a=0 rise=0 down=0
[58] clk=0 rst_n=1 a=0 rise=0 down=0
[59] clk=1 rst_n=1 a=0 rise=0 down=0

在20时刻,clk=0,此时为下降沿,注意a由0->1,由于触发器在上升沿采样,因此在下一个上升沿,也就是21时刻,检测到了a的上升沿,因此rise拉高,并只维持了一个周期
在30时刻,clk=0,此时为下降沿,注意a由1->0,在下一个上升沿,也就是31时刻,检测到了a的下降沿,因此down拉高,并只维持了一个周期

7.2 System C 模式的Wrapper file

前文我们给出了-cc格式的wrapper file,现在给出System C模式,注意如果要以sc模式仿真,需要安装System C 库,本文不进行介绍,运行结果就不解释了

#include 
#include 
#include 
#include  
#include "Vtop.h"
int sc_main(int argc, char *argv[])
{

    if (false && argc && argv)
    {
    }
    Verilated::debug(0);
    Verilated::randReset(2);
    Verilated::traceEverOn(true);
    Verilated::commandArgs(argc, argv);
    ios::sync_with_stdio();
    sc_clock clk{"clk", 2, 0.5, 0, false};
    sc_signal rst_n;
    sc_signal a;
    sc_signal rise;
    sc_signal down;
    const std::unique_ptr top{new Vtop{"top"}};
    top->clk(clk);
    top->rst_n(rst_n);
    top->a(a);
    top->rise(rise);
    top->down(down);
    VerilatedVcdSc *tfp = nullptr;
    const char *flag = Verilated::commandArgsPlusMatch("trace");
    if (flag && 0 == strcmp(flag, "+trace"))
    {
        tfp = new VerilatedVcdSc;
        top->trace(tfp, 99); // Trace 99 levels of hierarchy
        tfp->open("wave.vcd");
    }
    while (!Verilated::gotFinish())
    {
        if (tfp)
            tfp->flush();
        if (sc_time_stamp() >= sc_time(0, SC_NS) && sc_time_stamp() < sc_time(10, SC_NS))
        {
            rst_n = 0; // Assert reset
        }
        else
        {
            rst_n = 1; // Deassert reset
        }
        if (sc_time_stamp() > sc_time(15, SC_NS) && sc_time_stamp() < sc_time(20, SC_NS))
        {
            a = 0;
        }
        if (sc_time_stamp() >= sc_time(20, SC_NS) && sc_time_stamp() < sc_time(30, SC_NS))
        {
            a = 1;
        }
        if (sc_time_stamp() >= sc_time(30, SC_NS) && sc_time_stamp() < sc_time(40, SC_NS))
        {
            a = 0;
        }
        if (sc_time_stamp() >= sc_time(60, SC_NS))
        {
            break;
        }
        // Simulate 1ns
        sc_start(1, SC_NS);
        cout << "[" << sc_time_stamp() << "]"
             << " clk=" << top->clk << " rst_n=" << top->rst_n << " a=" << top->a << " rise=" << top->rise << " down=" << top->down << endl;
    }
    top->final();
    if (tfp)
    {
        tfp->close();
        tfp = nullptr;
    }
    return 0;
}

sc_clock是声明时钟,sc_signal声明变量,除了bool还有其他类型,sc_start(1, SC_NS)则是让时钟+1,更多函数可查阅systemc,网上资料很多,sc_main.cpp还增加调用了verilated_vcd_sc.h,将这些IO的状态导出为波形vcd

    verilator -Wall --trace -sc -exe top.v sc_main.cpp
    make -j -C obj_dir -f Vtop.mk
    ./obj_dir/Vtop +trace

8. 查看波形

波形的格式有很多种,例如供Verdi使用的fsdb,供gtkwave查看的vcd,在此,我们介绍使用gtkwave查看生成的wave.vcd,前面c++模式和sc模式都生成了vcd,任选其一

安装与使用gtkwave

sudo apt install gtkwave
gtkwave wave.vcd

Verilator简介与使用_第6张图片

上升沿和下降沿检测正确,结果也符合前面的分析

9. 参考资料

官方手册

https://verilator.org/guide/latest/index.html

github示例代码

https://github.com/verilator/verilator/tree/master/examples

Sc模式下的一些模块和函数可以参考《System C入门》巴斯克,夏宇闻译

有问题请指教

你可能感兴趣的:(FPGA,fpga开发,c++)