跳转至

仿真框架使用手册

非 LA32R 特供

选择 RV32I 或 LA32R 指令集的同学都可以使用此仿真框架。

Difftest

本仿真框架旨在于提供 Difftest 功能。对于 Difftest,你可以理解为,我们使用一个参考 CPU 与你实现的 CPU 进行对比,一旦两者不一致,我们就输出错误信息并停止仿真。

使用步骤

平台相关

Windows 未经过可行性测试,且 Verilator 在 Windows 的安装较为繁杂,不推荐

1. 安装依赖项

本仿真框架所需要的依赖项包括 Git, CMake 和 Verilator。对于 Git 和 CMake,你可以简单地使用 APT 安装:

$ sudo apt install git cmake

对于 Verilator,由于 APT 提供的版本较旧,你需要参照我们的这篇文档手动编译安装。

建议使用 Vlab 虚拟机

你可能已经在 Verilator 安装的教程页面看到了“Vlab 虚拟机已部署此工具”的提示框。使用 Vlab 虚拟机可以省去自行手动编译安装 Verilator 的过程。

你可以点此跳转至 Vlab 页面。

2. 获取实验框架

本实验框架存放在 GitHub 仓库 https://github.com/USTC-Loong-COD/COD25-Simulation-Framework 中。

你需要使用以下命令进行克隆:

$ git clone https://github.com/USTC-Loong-COD/COD25-Simulation-Framework

3. 添加 CPU 实现文件

打开仿真框架目录,将你的 CPU 实现文件放到 vsrc/your_vsrc 目录下;

如果你使用了 Verilog 的头文件,请将其放在 vsrc/your_include 目录下;

Image title

添加 CPU 实现文件

Warning

你的 CPU 接口应当与框架介绍中给出的框架保持一致。

该接口同样也适用于上板验证。

4. CPU 与仿真框架的适配

仿真框架内部使用 C++ 搭建了一个经过正确性测试的 CPU(以下统称标准核)。在运行仿真时,框架会首先将用户编写的 Verilog 代码翻译成 C++ 代码(以下统称用户核)。接下来,框架会单步运行用户核与标准核,并比较二者运行的状态。如果不一致,框架会记录相应的 CPU 状态并告知用户,从而高效定位设计中可能存在的问题。

这里涉及到一个问题:如何精确描述 CPU 的状态呢?自然,我们可以记录 CPU 内部所有的信号,然后挨个进行对比。然而不同用户的设计也不完全相同,我们也不能统一各个信号的名称与作用,所以这样的思路是不现实的。事实上,CPU 的状态可以由 PC、寄存器堆和存储器的写入状态完全确定:只要保证 CPU 正确写入了这些组件,就可以保证 CPU 运行的正确性。比如:如果 ALU 结果算错了,那么最后写回寄存器堆的数据就会出错;如果分支跳转出错了,那么写入 PC 的数据就会出错;如果访存地址算错了,那么写入数据存储器的地址就会错误。

我们的仿真框架正是基于这样的思想设计的:比较每一条指令运行完成后,标准核与用户核在 PC、寄存器堆和存储器的写入状态上的异同,即可判断用户核是否存在潜在的问题。

回到我们 CPU 的端口上:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
output                  [ 0 : 0]            commit,
output                  [31 : 0]            commit_pc,
output                  [31 : 0]            commit_instr,
output                  [ 0 : 0]            commit_halt,
output                  [ 0 : 0]            commit_reg_we,
output                  [ 4 : 0]            commit_reg_wa,
output                  [31 : 0]            commit_reg_wd,
output                  [ 0 : 0]            commit_dmem_we,
output                  [31 : 0]            commit_dmem_wa,
output                  [31 : 0]            commit_dmem_wd,

input                   [ 4 : 0]            debug_reg_ra,
output                  [31 : 0]            debug_reg_rd

这些端口中,commit 系列的端口即为每一条指令执行完成后,提交(Commit)给仿真框架的写状态信号。其中:

  • commit 端口用于指示当前是否有一条指令执行完成。在单周期 CPU 中,只要 PC 发生了变化,就必然有一条指令执行完成(因为一个周期执行一条指令),所以我们在后面的代码中将其设置为 1。
  • commit_pc 为这条指令的 PC。
  • commit_instr 为这条指令本身。
  • commit_halt 用于指示这条指令是否是指令序列中的最后一条指令。你可能已经注意到了,Lars 在编译时会在汇编代码末尾添加一条指令 0x80000000,这条指令是我们约定的 HALT 指令,用于指示当前程序已经运行完成。仿真框架在获得这个信号后就会停止比对。
  • commit_reg 系列用于指示寄存器堆的写状态。
  • commit_dmem 系列用于指示数据存储器的写状态。

debug 系列的端口用于仿真框架进行更为具体的比对操作,即判断用户核的 32 个寄存器的数值是否与标准核一致。

为了同步比对的时序,我们将上述信号在 CPU 中暂存一级后给出。因此,CPU 模块的最后需要添加如下的代码:

适配框架的代码
 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
reg  [ 0 : 0]   commit_reg          ;
reg  [31 : 0]   commit_pc_reg       ;
reg  [31 : 0]   commit_instr_reg    ;
reg  [ 0 : 0]   commit_halt_reg     ;
reg  [ 0 : 0]   commit_reg_we_reg   ;
reg  [ 4 : 0]   commit_reg_wa_reg   ;
reg  [31 : 0]   commit_reg_wd_reg   ;
reg  [ 0 : 0]   commit_dmem_we_reg  ;
reg  [31 : 0]   commit_dmem_wa_reg  ;
reg  [31 : 0]   commit_dmem_wd_reg  ;

always @(posedge clk) begin
    if (rst) begin
        commit_reg          <= 1'H0;
        commit_pc_reg       <= 32'H0;
        commit_instr_reg    <= 32'H0;
        commit_halt_reg     <= 1'H0;
        commit_reg_we_reg   <= 1'H0;
        commit_reg_wa_reg   <= 5'H0;
        commit_reg_wd_reg   <= 32'H0;
        commit_dmem_we_reg  <= 1'H0;
        commit_dmem_wa_reg  <= 32'H0;
        commit_dmem_wd_reg  <= 32'H0;
    end
    else if (global_en) begin
        // !!!! 请注意根据自己的具体实现替换 <= 右侧的信号 !!!!
        commit_reg          <= 1'H1;                        // 不需要改动
        commit_pc_reg       <= cur_pc;                      // 需要为当前的 PC
        commit_instr_reg    <= cur_inst;                    // 需要为当前的指令
        commit_halt_reg     <= cur_inst == `HALT_INST;      // 注意!请根据指令集设置 HALT_INST!
        commit_reg_we_reg   <= rf_we;                       // 需要为当前的寄存器堆写使能
        commit_reg_wa_reg   <= rf_wa;                       // 需要为当前的寄存器堆写地址
        commit_reg_wd_reg   <= rf_wd;                       // 需要为当前的寄存器堆写数据
        commit_dmem_we_reg  <= 0;                           // 不需要改动
        commit_dmem_wa_reg  <= 0;                           // 不需要改动
        commit_dmem_wd_reg  <= 0;                           // 不需要改动
    end
end

assign commit               = commit_reg;
assign commit_pc            = commit_pc_reg;
assign commit_instr         = commit_instr_reg;
assign commit_halt          = commit_halt_reg;
assign commit_reg_we        = commit_reg_we_reg;
assign commit_reg_wa        = commit_reg_wa_reg;
assign commit_reg_wd        = commit_reg_wd_reg;
assign commit_dmem_we       = commit_dmem_we_reg;
assign commit_dmem_wa       = commit_dmem_wa_reg;
assign commit_dmem_wd       = commit_dmem_wd_reg;

HALT 指令

请根据你所使用的指令集正确设置 HALT 指令

为了保证汇编程序运行完成后,PDU 能够知道 CPU 已经运行完成,CPU 需要在程序执行完成后向 PDU 发出一个特殊的信号,在这里就是我们的 halt 信号。为此,我们可以在汇编程序最后添加一个特殊的「指令」,保证其不会被用在正常的汇编程序中即可。

  • 对于 RV32R 指令集,你可以使用 ebreak 指令,它对应的机器码是 0x00100073,因此我们需要将 HALT_INST 设置为 32'H00100073。你可以在汇编代码的最后添加一条 ebreak 指令,以标识程序的结束;
  • 对于 LA32R 指令集,我们的仿真框架规定了中断指令的机器码是 0x80000000,因此在硬件设计时需要保持一致。你需要手动在 COE 文件的末尾添加这条机器码。这样,当程序执行到这一条「指令」时,CPU 就可以产生 commit_halt 信号,进而被 PDU 捕捉到。

5. 修改内存初始化文件

创建 mem 目录下的 instr.inidata.ini 文件,这两个文件的格式要求为:

  • 每一行一个 8 位 16 进制值;
  • 无 header
  • 不要使用逗号或分号等分隔符
  • 文件尾需要一个空行

你也可以添加别的文件作为两个存储器的初始化文件,只要符合上述要求即可。若如此做,你需要修改 vsrc/configs/configs.vh 中的 INSTR_MEM_INIDATA_MEM_INI 配置。

在本实验中,你需要在 instr.ini 中写入仿真汇编测试程序经过 RARS 导出 16 进制文件后的结果,data.ini 留空即可。

6. 修改配置

修改 include 目录下的 configs.hppvsrc/configs 目录下的 configs.vh,具体可修改的内容包括:

  • configs.hpp

    • 对应 ISA 的 MemoryConfigs:内存配置,包括指令和数据内存的起始地址、大小和初始化文件路径,一般使用默认值即可;

    • Configs::isa_type:使用的 ISA,可选项为 IsaType::RISC_VIsaType::LOONGARCH

    • Configs::core_type:核心的类型,可选项为 CoreType::SIMPLECoreType::COMPLETE,对应简单单周期和两个完整核心的实验;

    • Configs::difftest_level:对拍测试的等级,可选项为 DifftestLevel::NONEDifftestLevel::COMMITDifftestLevel::FULL,分别对应无对拍测试、只对比提交内容和对比完整 CPU 状态;

      对拍测试能帮助你快速定位到 CPU 设计的问题,但对比完整 CPU 状态会使运行速度大幅下降。检查时需开启对比完整 CPU 状态,并运行通过;

    • dump_waveform:是否开启波形图导出,设置为 true 会导出波形图。导出波形图会大幅降低仿真运行速度,并可能会生成很大的波形图文件,请谨慎决定是否开启;

    • dump_path:导出波形图的文件路径,使用相对路径和绝对路径均可。

  • configs.vh

    • INSTR_MEM_*DATA_MEM_*:内存配置,和 configs.hpp 中对应 ISA 的配置保持一致即可 ,虽然直接使用默认值大概率也不会出问题

    • CORE_TYPE:核心类型(和 configs.hpp 里面的不是一回事),可选项为 SINGLE_CYCLEPIPELINE,对应单周期 CPU 和流水线 CPU。

7. 编译

仿真框架根目录下使用如下命令进行编译:

$ cmake -B build && cmake --build build

如果编译出现与 fmt 库相关的报错,请使用 sudo apt install libfmt-dev 命令安装 fmt 库。

注意:每次修改 Verilog 源文件,或者修改配置项都需要重新编译!以及最新版的 verilator 可能有问题,如果发现修改了 Verilog 源文件后 generated 目录未更新,请删除该目录重新编译。

以下是助教的输出

(base) tsincy@tsincy-VMware-Virtual-Platform:~/lab/cod/COD25-Simulation-Framework$ cmake -B build && cmake --build build
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Performing Test _faligned_new
-- Performing Test _faligned_new - Success
-- Performing Test _fbracket_depth_4096
-- Performing Test _fbracket_depth_4096 - Failed
-- Performing Test _fcf_protection_none
-- Performing Test _fcf_protection_none - Success
-- Performing Test _mno_cet
-- Performing Test _mno_cet - Failed
-- Performing Test _Qunused_arguments
-- Performing Test _Qunused_arguments - Failed
-- Performing Test _Wno_bool_operation
-- Performing Test _Wno_bool_operation - Success
-- Performing Test _Wno_c__11_narrowing
-- Performing Test _Wno_c__11_narrowing - Success
-- Performing Test _Wno_constant_logical_operand
-- Performing Test _Wno_constant_logical_operand - Success
-- Performing Test _Wno_non_pod_varargs
-- Performing Test _Wno_non_pod_varargs - Success
-- Performing Test _Wno_parentheses_equality
-- Performing Test _Wno_parentheses_equality - Success
-- Performing Test _Wno_shadow
-- Performing Test _Wno_shadow - Success
-- Performing Test _Wno_sign_compare
-- Performing Test _Wno_sign_compare - Success
-- Performing Test _Wno_subobject_linkage
-- Performing Test _Wno_subobject_linkage - Success
-- Performing Test _Wno_tautological_bitwise_compare
-- Performing Test _Wno_tautological_bitwise_compare - Success
-- Performing Test _Wno_tautological_compare
-- Performing Test _Wno_tautological_compare - Success
-- Performing Test _Wno_uninitialized
-- Performing Test _Wno_uninitialized - Success
-- Performing Test _Wno_unused_but_set_parameter
-- Performing Test _Wno_unused_but_set_parameter - Success
-- Performing Test _Wno_unused_but_set_variable
-- Performing Test _Wno_unused_but_set_variable - Success
-- Performing Test _Wno_unused_parameter
-- Performing Test _Wno_unused_parameter - Success
-- Performing Test _Wno_unused_variable
-- Performing Test _Wno_unused_variable - Success
-- Performing Test _mt
-- Performing Test _mt - Failed
-- Performing Test _pthread
-- Performing Test _pthread - Success
-- Performing Test _lpthread
-- Performing Test _lpthread - Success
-- Performing Test _latomic
-- Performing Test _latomic - Success
-- Found ccache: /usr/bin/ccache ("ccache version 4.9.1")
-- Executing Verilator...
-- Configuring done (4.7s)
-- Generating done (0.0s)
-- Build files have been written to: /home/tsincy/lab/cod/COD25-Simulation-Framework/build
[  5%] Building CXX object CMakeFiles/sim.dir/app/main.cpp.o
[ 11%] Building CXX object CMakeFiles/sim.dir/generated/VTop.cpp.o
[ 16%] Building CXX object CMakeFiles/sim.dir/generated/VTop___024root__DepSet_hc590b6dd__0.cpp.o
[ 22%] Building CXX object CMakeFiles/sim.dir/generated/VTop___024root__DepSet_h0d2e5939__0.cpp.o
[ 27%] Building CXX object CMakeFiles/sim.dir/generated/VTop__ConstPool_0.cpp.o
[ 33%] Building CXX object CMakeFiles/sim.dir/generated/VTop___024root__Slow.cpp.o
[ 38%] Building CXX object CMakeFiles/sim.dir/generated/VTop___024root__DepSet_hc590b6dd__0__Slow.cpp.o
[ 44%] Building CXX object CMakeFiles/sim.dir/generated/VTop___024root__DepSet_h0d2e5939__0__Slow.cpp.o
[ 50%] Building CXX object CMakeFiles/sim.dir/generated/VTop__Trace__0.cpp.o
[ 55%] Building CXX object CMakeFiles/sim.dir/generated/VTop__Syms.cpp.o
[ 61%] Building CXX object CMakeFiles/sim.dir/generated/VTop__Trace__0__Slow.cpp.o
[ 66%] Building CXX object CMakeFiles/sim.dir/generated/VTop__TraceDecls__0__Slow.cpp.o
[ 72%] Building CXX object CMakeFiles/sim.dir/usr/local/share/verilator/include/verilated.cpp.o
[ 77%] Building CXX object CMakeFiles/sim.dir/usr/local/share/verilator/include/verilated_vcd_c.cpp.o
[ 83%] Building CXX object CMakeFiles/sim.dir/usr/local/share/verilator/include/verilated_threads.cpp.o
[ 88%] Linking CXX executable sim
[100%] Built target sim

8. 运行

编译出的可执行文件在 build 目录下,通常情况下,你需要在项目根目录下运行:

./build/sim

Image title

运行

波形图使用步骤

1. 安装依赖项

要使用仿真框架中生成的波形图,需要用到 gtkwave,你可以简单地使用 APT 安装:

$ sudo apt install gtkwave

2. 打开波形图

安装好 gtkwave 后,你可以在命令行使用:

$ gtkwave <vcd-file-path>

或者双击波形图文件来打开波形图。

3. 将信号添加到界面

使用 gtkwave 后打开波形图,你将看到下面的界面:

Image title

运行

该界面与 Vivado 的波形图界面十分类似。你可以在左侧上方的窗口中看到模块的结构,在左侧下方窗口中看到模块中的信号。选中(ctrl+a 全选,按住 ctrl 可多选)想要观察的信号后,单击下方的 Append 或 Insert 即可将其添加进右侧的波形出窗口中。界面上方还有一些其他的功能按钮,你可以参考 Vivado 仿真教程进行使用。

评论