【Verilator】 1 简明教程

  • Post author:
  • Post category:其他


我是


雪天鱼


,一名FPGA爱好者,研究方向是FPGA架构探索和数字IC设计。

欢迎来关注我的B站账号,我将定期更新IC设计教程。

B站账号:

雪天鱼



https://space.bilibili.com/397002941?spm_id_from=333.1007.0.0



1 准备工作

先从GitHub下载实验代码

git clone https://github.com/n-kremeris/verilator_basics
git checkout verilator_pt1



2 我们的DUT

以一个用SystemVerilog编写的简单ALU来作为DUT(device under test)来学习Verilator是如何工作的。下面是ALU的源码:

/****** alu.sv ******/
typedef enum logic [1:0] {
     add     = 2'h1,
     sub     = 2'h2,
     nop     = 2'h0
} operation_t /*verilator public*/;

module alu #(
        parameter WIDTH = 6
) (
        input clk,
        input rst,

        input  operation_t  op_in,
        input  [WIDTH-1:0]  a_in,
        input  [WIDTH-1:0]  b_in,
        input               in_valid,

        output logic [WIDTH-1:0]  out,
        output logic              out_valid
);

        operation_t  op_in_r;
        logic  [WIDTH-1:0]  a_in_r;
        logic  [WIDTH-1:0]  b_in_r;
        logic               in_valid_r;
        logic  [WIDTH-1:0]  result;

        // Register all inputs
        always_ff @ (posedge clk, posedge rst) begin
                if (rst) begin
                        op_in_r     <= '0;
                        a_in_r      <= '0;
                        b_in_r      <= '0;
                        in_valid_r  <= '0;
                end else begin
                        op_in_r    <= op_in;
                        a_in_r     <= a_in;
                        b_in_r     <= b_in;
                        in_valid_r <= in_valid;
                end
        end

        // Compute the result
        always_comb begin
                result = '0;
                if (in_valid_r) begin
                        case (op_in_r)
                                add: result = a_in_r + b_in_r;
                                sub: result = a_in_r + (~b_in_r+1'b1);
                                default: result = '0;
                        endcase
                end
        end

        // Register outputs
        always_ff @ (posedge clk, posedge rst) begin
                if (rst) begin
                        out       <= '0;
                        out_valid <= '0;
                end else begin
                        out       <= result;
                        out_valid <= in_valid_r;
                end
        end

endmodule;

创建一个工作目录,并将上述代码保存为

alu.sv

.

这个ALU非常简单。只支持两种操作:加法和减法。操作经过两个周期计算出最终结果,下面是期望波形:

enter description here



3 将 SystemVerilog 转换为 C++

由于Verilator要求C++ testbench 被编译成本地系统二进制。然而,我们不能将SystemVerilog编写的 ALU添加进C++ testbench:我们首先需要使用 Verilator 将 SystemVerilog 代码转换为C++,或者说

"Verilate"

,执行一行代码即可:

 verilator --cc alu.sv

enter description here

通过执行该指令,会生成一个名为

obj_dir

的子目录,里面是转换所生成的文件:

enter description here

jccao@jccao-vm:~/jccao/files/code/verilator/part1$ ls  obj_dir/
Valu___024root__DepSet_h7172bd91__0.cpp        Valu_classes.mk
Valu___024root__DepSet_h7172bd91__0__Slow.cpp  Valu.cpp
Valu___024root__DepSet_ha59b247d__0.cpp        Valu.h
Valu___024root__DepSet_ha59b247d__0__Slow.cpp  Valu.mk
Valu___024root.h                               Valu__Syms.cpp
Valu___024root__Slow.cpp                       Valu__Syms.h
Valu___024unit__DepSet_h45503383__0__Slow.cpp  Valu__ver.d
Valu___024unit.h                               Valu__verFiles.dat
Valu___024unit__Slow.cpp

这里需要关注的文件有:


  1. Valu.cpp

    ,

    Valu.h

    : alu转换后对应的C++实现源文件以及头文件,其中

    Valu.h

    包含了转换后的 “ALU “类定义–也就是我们将在C++ testbench 中作为DUT “实例化 “的东西。

  2. Valu.mk

    : 用于编译可执行的仿真文件

  3. Valu___024unit.h

    : 这是 “ALU “类的内部头文件,包含了

    operation_t

    数据类型定义。



4 简单 testbench 设计

testbench 命名为

tb_alu.cpp

,最简单的 testbench 代码如下所示:

#include <stdlib.h>
#include <iostream>
#include <verilated.h>
#include <verilated_vcd_c.h>
#include "Valu.h"
#include "Valu___024unit.h"

#define MAX_SIM_TIME 20  // 仿真总时钟边沿数
vluint64_t sim_time = 0; // 用于计数时钟边沿

int main(int argc, char** argv, char** env) {
    Valu *dut = new Valu; // 例化转换后的 ALU 模块
	  // 接下来的四行代码用于设置波形存储为VCD文件
    Verilated::traceEverOn(true);
    VerilatedVcdC *m_trace = new VerilatedVcdC;  
    dut->trace(m_trace, 5);               
    m_trace->open("waveform.vcd");
	  // 实际进行仿真的代码
    while (sim_time < MAX_SIM_TIME) {
        dut->clk ^= 1; 
        dut->eval();  
        m_trace->dump(sim_time);
        sim_time++; // 更新仿真时间
    }

    m_trace->close();
    delete dut;
    exit(EXIT_SUCCESS);
}
  1. testbench 需要包含

    <verilated.h>



    <verilated_vcd_c.h>

    ,这两个头文件在安装好verilator就有了,前者里面包含了常用API,后者包含将波形写入

    VCD(value change dump)

    文件的API
  2. 刚说过

    "Valu.h"

    包含了 verilated 后 ALU模块的类定义,

    "Valu___024unit.h"

    包含

    operation_t

    数据类型定义,被ALU类所需要,所以需要在Testbench中包含这两个头文件

  3. dut->trace(m_trace, 5);

    的意思是将 m_trace 传递给 dut,5表示跟踪深度限制在DUT的5级以内,这个5级目前我理解为DUT的子模块层级。
while (sim_time < MAX_SIM_TIME) {
        dut->clk ^= 1;
        dut->eval();
        m_trace->dump(sim_time);
        sim_time++;
    }

该部分是实际启动仿真的代码。


  • dut->clk ^= 1

    的意思是 clk 与 1 异或,翻转时钟

  • dut->eval()

    eval()函数更新电路的状态,可理解为仿真 ALU 模型中的所有信号

  • m_trace->dump(sim_time)

    将所有被追踪的信号写入波形中



5 生成仿真执行文件

完成 testbench 编写后,我们需要 build 可执行文件来运行这个仿真。

这是因为 Verilator 不同于

Modelsim

这样的仿真软件,内置仿真器进行仿真,Verilator本身就是只进行转换和编译工作,即

verilate

, 仿真工作由C++可执行文件完成的,所以我们需要用

make

编译 testbench得到可执行文件。


testbench 和转换后的HDL编写的模块本质上是一个C++应用程序,它可以在你的计算机上建立和运行。运行编译后的可执行文件就是模拟你的设计

,用GNU编译器集(GCC)构建Verilator可执行文件。

现在编译我们的测试文件并进行仿真,此时需要运行 Verilator 并重新生成包含测试用例的 .mk 文件:

$ verilator -Wall --trace -cc alu.sv --exe tb_alu.cpp
  • -Wall 表示开启 C++ 所有警告
  • –trace 表示开启波形跟踪
  • -cc 转换.sv为cpp
  • –exe 根据后面指定的testbench 生成用于仿真的可执行文件

随后我们进行编译:

make -C obj_dir -f Valu.mk Valu

  • -C obj_dir

    告诉

    make

    工作目录为

    obj_dir
  • makefile 通过

    -f

    指定
  • 最后的

    Valu

    则是 target,也就是编译生成的testbench可执行文件的name

编译成功的话,可以在

obj_dir

目录下找到

Valu

可执行文件

enter description here



6 运行 testbench



./obj_dir/Valu

命令执行可执行文件进行仿真,此时会生成波形图

waveform.vcd

,我们只需要执行

gtkwave waveform.vcd

即可查看波形图。

enter description here

至此你已经完成了Verilator的一个基础仿真实验,也对Verilator有了一定的认知。

gtkwave安装:


sudo apt-get install gtkwave



7 问题思考

可以看到我们的仿真没有任何’x’,或未知值。这是因为

Verilator

是一个双状态的仿真工具,默认情况下,所有的信号都被初始化为0。这对于提高仿真速度来说是有效的,因为2个状态比4个状态要少,但是如果我们想检查我们的复位逻辑工作得如何,就不是很好。我们将在后面进一步探讨这个问题。



版权声明:本文为qq_44447544原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。