• 【Verilator】 1 简明教程


    我是 雪天鱼,一名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
    
    • 1
    • 2

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    创建一个工作目录,并将上述代码保存为alu.sv.
    这个ALU非常简单。只支持两种操作:加法和减法。操作经过两个周期计算出最终结果,下面是期望波形:

    enter description here

    3 将 SystemVerilog 转换为 C++

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

     verilator --cc alu.sv
    
    • 1

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里需要关注的文件有:

    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 
    #include 
    #include 
    #include 
    #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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    1. testbench 需要包含 ,这两个头文件在安装好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++;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    • 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
    
    • 1
    • -Wall 表示开启 C++ 所有警告
    • –trace 表示开启波形跟踪
    • -cc 转换.sv为cpp
    • –exe 根据后面指定的testbench 生成用于仿真的可执行文件

    随后我们进行编译:

    make -C obj_dir -f Valu.mk Valu
    
    • 1
    • -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个状态要少,但是如果我们想检查我们的复位逻辑工作得如何,就不是很好。我们将在后面进一步探讨这个问题。

  • 相关阅读:
    【C语言进阶】文件操作(一)
    [附源码]计算机毕业设计网约车智能接单规划小程序Springboot程序
    2023.9.19 关于 数据链路层 和 DNS 协议 基本知识
    Java并发编程--多线程间的同步控制和通信
    【计算机毕设之基于Java的贫困生资助管理系统-哔哩哔哩】 https://b23.tv/LrumkKI
    Linux 关闭防火墙
    数据库缓存策略
    java毕业设计健民中医药方网设计(附源码、数据库)
    Ubuntu20.04 /dev/nvme0n1p5: clean,nnnnn/mmmmmfiles,nnnnnnn/mmmmmmblocks 解决过程
    JAVA毕业设计河南口腔医疗机构线上服务系统计算机源码+lw文档+系统+调试部署+数据库
  • 原文地址:https://blog.csdn.net/qq_44447544/article/details/127781609