【一生一芯】Verilator上手

搭建完Verilator的环境之后,就该考虑去上手实操运行一些实例了。这里参考了官方手册It's embedded以及B站上一些资料。

一、创建一个二级制执行文件——Hello World

我们可以将System Verilog代码编译成二进制文件让Verilator执行。

进入“YSYX”文件夹,新建一个“test_our”文件夹:

cd ~/Desktop/YSYX
mkdir test_our

然后新建一个Verilog文件:

cat >our.v <<'EOF'
module our;
initial begin $display("Hello World"); $finish; end
endmodule
EOF

使用Verilator来运行我们编写的这个小例子:

verilator --binary -j 0 -Wall our.v

接着运行我们编译出来的文件:

obj_dir/Vou

可以看到终端返回Hello World:

Hello World
- our.v:2: Verilog $finish

二、创建一个C++执行文件

接着上一节的内容,在“test_our”路径下新建一个C++文件:

cat >sim_main.cpp <<'EOF'
#include "Vour.h"
#include "verilated.h"
int main(int argc, char** argv) {
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vour* top = new Vour{contextp};
while (!contextp->gotFinish()) { top->eval(); }
delete top;
delete contextp;
return 0;
}
EOF

然后使用Verilator运行这个例子:

verilator --cc --exe --build -j 0 -Wall sim_main.cpp our.v

Verilator运行完成后,C++文件到了“obj_dir”这个目录下。那么我们运行刚才生存的文件:

obj_dir/Vour

返回以下结果:

Hello World
- our.v:2: Verilog $finish

最好使用Makefile来帮助你执行上述步骤,以免在修改源码之后又要重复执行上述步骤,Makefile能帮助你执行所有需要的步骤。

三、编写一个简单的CmakeLists

Verilator可以使用Cmake运行,在“example”目录下有CMake的例子可以参考。接下来写一个简短的Cmakelists.txt文件(提示,使用Cat指令,或者直接用文本编辑器新建一个存放在“test_our”目录下):

cmake_minimum_required(VERSION 3.22)   #手册中没有这一行,但是Cmake要求每个Cmakelists开头必须添加这一行,否则报错
project(cmake_example)
find_package(verilator HINTS $ENV{VERILATOR_ROOT})
add_executable(Vour sim_main.cpp)
verilate(Vour SOURCES our.v)

接着我们新建一个build目录并运行Cmake:

mkdir build
cd build
cmake ..
cmake --build .

提示,如果缺少Cmake要先安装Cmake:

sudo apt install cmake

接着运行生成的文件:

./Vour

返回Hello World:

Hello World
- our.v:2: Verilog $finish

四、一个DUT(device under test)实例

首先获取源码,并切换到pt1分支:

cd ~/Desktop/YSYX
git clone https://github.com/n-kremeris/verilator_basics
cd verilator_basics
git checkout verilator_pt1

通过一个DUT实例,可以更加仔细地学习Verilator究竟是怎样工作的,上述操作获取的代码中已经存在一个DUT了。打开alu.sv可以看到以下Verilog代码:

/*
 * Primitive alu example for Verilator example
 *
 * File name: alu.sv
 * Author: Norbertas Kremeris 2021
 *
 */
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     <= nop;      // 这里需要改称nop,否则会报Error
                        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;
/* verilator lint_off EOFNEWLINE */
// 记得末尾要用回车空一行,否则会报Error


如你所见,alu文件中包含的电路功能十分简单,他只有两个寄存器,没有延迟,只支持两个操作——加或减。以下是我们期待alu电路行为的波形图:

 

在第一个阶段里,输入量都被存在输入寄存器中。在第二阶段,寄存器的组合结果被存在输出寄存器中。心中对此有了大概之后,就可以开始编写测试文件。

由于alu是Verilog文件,我们需要借助Verilator将其转换为C++文件:

 verilator --cc alu.sv

“--CC“意味着告诉Verilator将alu文件转化为C++文件,当然也可以使用”--SC“将其转化为C文件。

接下来我们可以写测试文件了,打开名为“tb_alu.cpp”的测试文件,可以看到如下代码:

// Verilator Example
// Norbertas Kremeris 2021
#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;

    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);
}

接下来建立仿真的可执行文件:

verilator -Wall --trace -cc alu.sv --exe tb_alu.cpp

编译它:

make -C obj_dir -f Valu.mk Valu

运行:

./obj_dir/Valu

查看波形:

gtkwave waveform.vcd

至此,已经完成了整个DUT的仿真过程。也大致接触/理解了Verilator的一些基本用法。

 五、双控开关

新建一个文件夹用于存放工程:

cd ~/Desktop/YSYX/ysyx-workbench
mkdir On_OFF_Switch
cd On_OFF_Switch

新建描述双控开关的Verilog文件:

cat >Switch.v <<'EOF'
module top
(
  input a,
  input b,
  output f
);

assign f = a ^ b;

endmodule
EOF

 

posted @ 2023-06-01 16:51  LyricsWu  阅读(3448)  评论(0编辑  收藏  举报