1. 简介
Verilator 是一个开源的 Verilog 和 SystemVerilog 硬件描述语言 (HDL) 仿真器。它是一个高性能的仿真器,可以将 Verilog 和 SystemVerilog 代码转换为 C++/SystemC 代码,并生成可执行的仿真模型。
Verilator 的主要特点包括:
- 高性能:Verilator 生成的仿真模型具有非常高的性能,可以与商业级仿真器媲美。
 - 开源:Verilator 是一个开源项目,可以免费使用和修改。
 - 跨平台:Verilator 可以在 Linux、macOS 和 Windows 等多种操作系统上运行。
 - 支持多种 HDL:Verilator 不仅支持 Verilog,还支持 SystemVerilog 语言。
 - 可扩展性:Verilator 提供了丰富的 API,可以与其他工具和环境集成。
 
Verilator 的主要应用场景包括:
- 硬件设计和验证:Verilator 可以用于设计和验证各种硬件电路,包括 FPGA、ASIC 和嵌入式系统等。
 - 软件开发:Verilator 生成的仿真模型可以用于软件开发和测试,如驱动程序和固件等。
 - 教学和研究:Verilator 可以用于教学和研究,帮助学生和研究人员了解和学习 HDL 语言。 
2. 安装
Linux下可以通过apt安装Verilator,但是安装的版本较旧,新版本功能更强大,Bug更少,推荐通过代码编译安装最新版本。此处安装gcc-9是为了后续编译gcc-5.2做准备,所以未安装最新版本gcc。Linux使用22.04版本。
sudo apt install -y gcc-9 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 10 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 10 sudo apt-get install -y git help2man perl python3 make cmake autoconf flex bison ccache sudo apt-get install -y libgoogle-perftools-dev numactl perl-doc sudo apt-get install -y libfl2 sudo apt-get install -y libfl-dev sudo apt-get install -y zlibc zlib1g zlib1g-dev git clone https://github.com/verilator/verilator cd verilator unset VERILATOR_ROOT git checkout -b v5.024 v5.024 autoconf ./configure make -j `nproc` sudo make install sudo apt install -y gtkwave # SystemC 2.3.3 curl -O https://www.accellera.org/images/downloads/standards/systemc/systemc-2.3.3.tar.gz tar -xzf systemc-2.3.3.tar.gz cd systemc-2.3.3 mkdir build cd build ../configure --prefix=/usr/local/systemc-2.3.3 make -j8 sudo make install配置SystemC:
在~/.bashrc最后面添加:
export SYSTEMC_INCLUDE=/usr/local/systemc-2.3.3/include
export SYSTEMC_LIBDIR=/usr/local/systemc-2.3.3/lib-linux64
export LD_LIBRARY_PATH=/usr/local/systemc-2.3.3/lib-linux64
在控制台输入:
source ~/.bashrc
 
3. 测试
3.1. 版本
m@vm-ubuntu22:~$ verilator --version
Verilator 5.024 2024-04-05 rev v5.024 
3.2. 示例
Verilator目录下的example目录下有一些示例,其中有_c表示是cpp版本示例,_sc表示是SystemC版本示例。
3.2.1. CPP示例
Verilator将Verilog转为标准C++来进行编译仿真,其仿真速度会更快。这种方式生成的仿真模块也可以更方便地接入QEMU等模块中进行使用。
cd make_tracing_c
make 
make会完成Verilog转C++,编译SystemC代码,仿真运行。
3.2.1.1. 编译
verilator -cc --exe --build -j top.v sim_main.cpp
- verilator -cc 表示将top.v生成cpp文件,并保存到OBJ_DIR目录。
 - -exe表示生成可执行文件。
 - sim_main.cpp表示此为启动文件,也即testbench文件。
 - --build指示通过make编译,make中g++的编译过程其实现的过程如下: 
ccache g++ -I. -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Os -c -o sim_main.o ../sim_main.cpp ccache g++ -Os -I. -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -c -o verilated.o /usr/local/share/verilator/include/verilated.cpp ccache g++ -Os -I. -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -c -o verilated_threads.o /usr/local/share/verilator/include/verilated_threads.cpp /usr/bin/python3 /usr/local/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include Vtop.cpp Vtop___024root__DepSet_h84412442__0.cpp Vtop___024root__DepSet_heccd7ead__0.cpp Vtop___024root__Slow.cpp Vtop___024root__DepSet_heccd7ead__0__Slow.cpp Vtop__Syms.cpp > Vtop__ALL.cpp ccache g++ -Os -I. -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -c -o Vtop__ALL.o Vtop__ALL.cpp echo "" > Vtop__ALL.verilator_deplist.tmp g++ sim_main.o verilated.o verilated_threads.o Vtop__ALL.a -pthread -lpthread -latomic -o Vtop - ccache是为了缓存加速编译的,可以不用管。
 - verilator的main入口函数在sim_main.cpp中。
 - verilated.cpp和verilated_threads.cpp是两个verilator文件。
 - Python脚本是将top.v生成的C++代码全部合并到Vtop__ALL.cpp,方便编译。
 - 最后编译链接生成可执行文件Vtop。
 
3.2.1.2. 仿真运行
./Vtop
3.2.1.3. testbench代码
int main(int argc, char** argv) {
    // This is a more complicated example, please also see the simpler examples/make_hello_c.
    // Create logs/ directory in case we have traces to put under it
    Verilated::mkdir("logs");
    // Construct a VerilatedContext to hold simulation time, etc.
    // Multiple modules (made later below with Vtop) may share the same
    // context to share time, or modules may have different contexts if
    // they should be independent from each other.
    // Using unique_ptr is similar to
    // "VerilatedContext* contextp = new VerilatedContext" then deleting at end.
    const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
    // Do not instead make Vtop as a file-scope static variable, as the
    // "C++ static initialization order fiasco" may cause a crash
    // Set debug level, 0 is off, 9 is highest presently used
    // May be overridden by commandArgs argument parsing
    contextp->debug(0);
    // Randomization reset policy
    // May be overridden by commandArgs argument parsing
    contextp->randReset(2);
    // Verilator must compute traced signals
    contextp->traceEverOn(true);
    // 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".
    // Using unique_ptr is similar to "Vtop* top = new Vtop" then deleting at end.
    // "TOP" will be the hierarchical name of the module.
    const std::unique_ptr<Vtop> top{new Vtop{contextp.get(), "TOP"}};
    // Set Vtop's input signals
    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;
    // Simulate until $finish
    while (!contextp->gotFinish()) {
        // Historical note, before Verilator 4.200 Verilated::gotFinish()
        // was used above in place of contextp->gotFinish().
        // Most of the contextp-> calls can use Verilated:: calls instead;
        // the Verilated:: versions just assume there's a single context
        // being used (per thread).  It's faster and clearer to use the
        // newer contextp-> versions.
        contextp->timeInc(1);  // 1 timeprecision period passes...
        // Historical note, before Verilator 4.200 a sc_time_stamp()
        // function was required instead of using timeInc.  Once timeInc()
        // is called (with non-zero), the Verilated libraries assume the
        // new API, and sc_time_stamp() will no longer work.
        // Toggle a fast (time/2 period) clock
        top->clk = !top->clk;
        // Toggle control signals on an edge that doesn't correspond
        // to where the controls are sampled; in this example we do
        // this only on a negedge of clk, because we know
        // reset is not sampled there.
        if (!top->clk) {
            if (contextp->time() > 1 && contextp->time() < 10) {
                top->reset_l = !1;  // Assert reset
            } else {
                top->reset_l = !0;  // Deassert reset
            }
            // Assign some other inputs
            top->in_quad += 0x12;
        }
        // Evaluate model
        // (If you have multiple models being simulated in the same
        // timestep then instead of eval(), call eval_step() on each, then
        // eval_end_step() on each. See the manual.)
        top->eval();
        // Read outputs
        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]);
    }
    // Final model cleanup
    top->final();
    // Final simulation summary
    contextp->statsPrintSummary();
    // Return good completion status
    // Don't use exit() or destructor won't get called
    return 0;
}
 
- VerilatedContext模块用来控制控制时钟的,包括运行时间、时间步进、结束等。
 - Vtop即对应Verilog中的Top模块,通过Vtop对象可以直接操作Top模块中的信号来完成对Top模块的验证。
 
3.2.2. SystemC示例
SystemC库提供了更友好的接口,并且可以直接接入SystemC建模的代码来进行仿真,通用性更好。其缺点是仿真速度不如纯C++版本。
cd make_tracing_sc
make
make会完成Verilog转SystemC,编译SystemC代码,仿真运行。
3.2.2.1. 编译
make的主要功能是:
verilator -sc --exe --build -j top.v sc_main.cpp
- -sc指示将top.v转换为SystemC代码。
 - --exe指定生成可执行文件。
 - sc_main.cpp表示这是一个SystemC的启动文件。
 
make -j -C obj_dir -f ../Makefile_obj
ccache g++  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=1 -DVM_SC=1 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable      -I/usr/local/systemc-2.3.2/include  -MMD -MP -DVL_DEBUG=1 -Wno-deprecated -Os -fstrict-aliasing -c -o sc_main.o ../sc_main.cpp
ccache g++ -Os  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=1 -DVM_SC=1 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable      -I/usr/local/systemc-2.3.2/include  -MMD -MP -DVL_DEBUG=1 -Wno-deprecated -c -o verilated.o /usr/local/share/verilator/include/verilated.cpp
ccache g++ -Os  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=1 -DVM_SC=1 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable      -I/usr/local/systemc-2.3.2/include  -MMD -MP -DVL_DEBUG=1 -Wno-deprecated -c -o verilated_cov.o /usr/local/share/verilator/include/verilated_cov.cpp
ccache g++ -Os  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=1 -DVM_SC=1 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable      -I/usr/local/systemc-2.3.2/include  -MMD -MP -DVL_DEBUG=1 -Wno-deprecated -c -o verilated_vcd_c.o /usr/local/share/verilator/include/verilated_vcd_c.cpp
ccache g++ -Os  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=1 -DVM_SC=1 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable      -I/usr/local/systemc-2.3.2/include  -MMD -MP -DVL_DEBUG=1 -Wno-deprecated -c -o verilated_threads.o /usr/local/share/verilator/include/verilated_threads.cpp
/usr/bin/python3 /usr/local/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include Vtop.cpp Vtop___024root__DepSet_h84412442__0.cpp Vtop___024root__DepSet_heccd7ead__0.cpp Vtop__Trace__0.cpp Vtop___024root__Slow.cpp Vtop___024root__DepSet_h84412442__0__Slow.cpp Vtop___024root__DepSet_heccd7ead__0__Slow.cpp Vtop__Syms.cpp Vtop__Trace__0__Slow.cpp Vtop__TraceDecls__0__Slow.cpp > Vtop__ALL.cpp
echo "" > Vtop__ALL.verilator_deplist.tmp
ccache g++ -Os -fstrict-aliasing  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=1 -DVM_SC=1 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable      -I/usr/local/systemc-2.3.2/include  -MMD -MP -DVL_DEBUG=1 -Wno-deprecated -c -o Vtop__ALL.o Vtop__ALL.cpp
g++     -L/usr/local/systemc-2.3.2/lib-linux64 sc_main.o verilated.o verilated_cov.o verilated_vcd_c.o verilated_threads.o Vtop__ALL.a    -pthread -lpthread -latomic  -lsystemc -o Vtop
 
- sc_main.cpp是SystemC的testbench文件。
 - verilated.cpp,verilated_cov.cpp,verilated_vcd_c.cpp,verilated_threads.cpp是Verilator的源码,主要提供时钟模块、覆盖率模块、vcd波形生成模块,以及线程并行模块。
 
- Python脚本是将top.v生成的C++代码全部合并到Vtop__ALL.cpp,方便编译。
 - 最后编译链接生成可执行文件Vtop。
 
3.2.2.2. 仿真运行

仿真运行会打印SystemC的版本信息。
通过GDB,我们可以看到真实的main入口函数是在SystemC模块中的。
3.2.2.3. testbench代码
int sc_main(int argc, char* argv[]) {
    // This is a more complicated example, please also see the simpler examples/make_hello_c.
    // Create logs/ directory in case we have traces to put under it
    Verilated::mkdir("logs");
    // Set debug level, 0 is off, 9 is highest presently used
    // May be overridden by commandArgs argument parsing
    Verilated::debug(0);
    // Randomization reset policy
    // May be overridden by commandArgs argument parsing
    Verilated::randReset(2);
#if VM_TRACE
    // Before any evaluation, need to know to calculate those signals only used for tracing
    Verilated::traceEverOn(true);
#endif
    // Pass arguments so Verilated code can see them, e.g. $value$plusargs
    // This needs to be called before you create any model
    Verilated::commandArgs(argc, argv);
    // General logfile
    std::ios::sync_with_stdio();
    // Define clocks
    sc_clock clk{"clk", 10, SC_NS, 0.5, 3, SC_NS, true};
    sc_clock fastclk{"fastclk", 2, SC_NS, 0.5, 2, SC_NS, true};
    // Define interconnect
    sc_signal<bool> reset_l;
    sc_signal<uint32_t> in_small;
    sc_signal<uint64_t> in_quad;
    sc_signal<sc_bv<70>> in_wide;
    sc_signal<uint32_t> out_small;
    sc_signal<uint64_t> out_quad;
    sc_signal<sc_bv<70>> out_wide;
    // Construct the Verilated model, from inside Vtop.h
    // Using unique_ptr is similar to "Vtop* top = new Vtop" then deleting at end
    const std::unique_ptr<Vtop> top{new Vtop{"top"}};
    // Attach Vtop's signals to this upper model
    top->clk(clk);
    top->fastclk(fastclk);
    top->reset_l(reset_l);
    top->in_small(in_small);
    top->in_quad(in_quad);
    top->in_wide(in_wide);
    top->out_small(out_small);
    top->out_quad(out_quad);
    top->out_wide(out_wide);
    // You must do one evaluation before enabling waves, in order to allow
    // SystemC to interconnect everything for testing.
    sc_start(SC_ZERO_TIME);
#if VM_TRACE
    // If verilator was invoked with --trace argument,
    // and if at run time passed the +trace argument, turn on tracing
    VerilatedVcdSc* tfp = nullptr;
    const char* flag = Verilated::commandArgsPlusMatch("trace");
    if (flag && 0 == std::strcmp(flag, "+trace")) {
        std::cout << "Enabling waves into logs/vlt_dump.vcd...\n";
        tfp = new VerilatedVcdSc;
        top->trace(tfp, 99);  // Trace 99 levels of hierarchy
        Verilated::mkdir("logs");
        tfp->open("logs/vlt_dump.vcd");
    }
#endif
    // Simulate until $finish
    while (!Verilated::gotFinish()) {
#if VM_TRACE
        // Flush the wave files each cycle so we can immediately see the output
        // Don't do this in "real" programs, do it in an abort() handler instead
        if (tfp) tfp->flush();
#endif
        // Apply inputs
        if (sc_time_stamp() > sc_time(1, SC_NS) && sc_time_stamp() < sc_time(10, SC_NS)) {
            reset_l = !1;  // Assert reset
        } else {
            reset_l = !0;  // Deassert reset
        }
        // Simulate 1ns
        sc_start(1, SC_NS);
    }
    // Final model cleanup
    top->final();
    // Close trace if opened
#if VM_TRACE
    if (tfp) {
        tfp->close();
        tfp = nullptr;
    }
#endif
    // Coverage analysis (calling write only after the test is known to pass)
#if VM_COVERAGE
    Verilated::mkdir("logs");
    VerilatedCov::write("logs/coverage.dat");
#endif
    // Return good completion status
    return 0;
}
 
- Verilated空间的接口用来控制环境,包括覆盖率、波形、参数处理、环境结束等。
 - Vtop对应Verilog的Top模块,可以直接操作Top模块的信号来进行赋值验证。
 - Top模块的信号,必须使用SystemC的风格来进行设置修改。
 
C++版本的代码思路和SystemC版本的思路基本一样,只是代码的操作形式略有差异。SystemC版本可以更方便地直接调用SystemC的建模代码。
3.2.3. 纯verilog示例
如果Top模块不需要传输信号,则可以使用纯verilog模块,即不需要编写C++代码。
Verilator将verilog转换为C++,并生成一个简单的main.cpp文件作为启动文件。其编译过程:
verilator --binary -j 0 top.v
3.2.4. 生成波形
生成波形需要VerilatedVcdSc模块。
    if (flag && 0 == strcmp(flag, "+trace"))
    {
        tfp = new VerilatedVcdSc;
        top->trace(tfp, 0); 
        tfp->open("wave.vcd");
    } 
在仿真运行时:
./obj_dir/Vtop +trace
3.2.5. 查看波形
gtkwave wave.vcd



















