• 【蜂鸟E203的FPGA验证】Chap.6 基于Iverilog的指令功能与流水线仿真


    前言:本文均为作者原创,内容均来自本人的毕业设计。未经授权严禁转载、使用。里面的插图和表格均为作者本人制作,如需转载请联系我并标注引用参考。分享仅供大家学习和交流。

    总结

      功能仿真主要检查代码中的语法错误以及代码行为的正确性,以及对源代码进行逻辑功能的验证。完成功能仿真之前,需要要先建立testbench,仿真结果将会以波形图或者文本文件形式给出相关结果报告。通过功能仿真可以及时发现设计中存在的错误,一般是不考虑器件延时和布线延时的理想情况下,所以这个阶段的仿真也可以做到与器件无关。
      本设计功能仿真所使用的数字仿真工具是Iverilog 12.0。在Ubuntu20.4的虚拟机系统环境下,首先安装仿真环境所需Linux工具包,如device-tree-compiler(设备树内核编译工具)、autotools-dev(交叉工具链编译)等。第二,安装RISC-V GNU工作链 (GNU Toolchain) ,是一组Linux操作系统内部的开发和编程工具,包括GCC、GNU Binutils和GNU make等,这些工具构成了一个完整的编程环境。第三,下载E203内核的源码并使用Iverilog12.0编译并综合网表,可以测试指令进行波形查看
      本设计的功能仿真利用了RISC-V官方的riscv-tests,能够检测指令集中的指令在处理器中运行结果是否成功,判断处理器的设计是否可以通过指令集架构定义的测试程序,仿真结果将会以波形图或者文本文件形式给出相关结果报告。所使用的数字仿真工具是Iverilog 12.0,具体实现步骤是做一个整体的testcase(测试用例),然后将处理器电路代码配置到Iverilog的仿真环境中,批量化运行所有准备好的测试用例都输入测试。而指令集功能仿真过程和测试结果,全部记录到日志文件(.log)中。

    指令集的功能验证

      对处理器核进行功能验证,包含若干个测试子程序用于测试RISC-V处理器的指令是否正确实现,并且每个子程序都带有自检代码,可以检测运行的正确与否。具体测试步骤如图3-2所示:
    在这里插入图片描述

    图3-2 RV32I指令集的验证流程图

    1. 测试程序的生成

      测试程序的生成:下载RISC-V官网的riscv-tests测试文件。首先用宏定义文件test_macros.h和riscv_test.h完成指令集中各种指令测试所需汇编代码(例如测试ADD指令add.s)以及其特定的链接脚本。链接脚本一般用来将目标汇编代码的文件等链接在一起,可以确认汇编代码在处理器指令的起始地址和操作内存大小。链接脚本把数据段、地址以及堆栈段等分配在系统内存中不同的地址空间。

    2. 对汇编代码进行链接

      对汇编代码进行链接:将汇编代码进行汇编生成ELF格式的可重定向文件,此文件不能直接运行,需要通过链接脚本对生成的文件进行链接,在RISC-V编译工具gnu-mcu-eclipse-nono-gcc中调用Binutils中的链接器来链接包含启动代码的目标文件(.ELF),此过程需要依赖库文件、引导程序以及链接脚本。

    3. 生成所需格式的文件

      生成可被Veilog的ReadMenh函数可读的文件xxx.veilog:使用Binutils中的pbject-copy对ELF目标文件进行反汇编生成包含指令和数据的dump文件机器可读的二进制文件(xxx.verilog),如图3-3所示。

    在这里插入图片描述
    在这里插入图片描述

    图3-3 add.elf文件生成的汇编文件.dump(左)与二进制文件.verilog(右)

    4. Iverilog对RTL代码进行编译

      对处理器设计的verilog的RTL代码进行编译,选择Iverilog仿真器对verilog编写的RTL代码进行编译。对编写的指令测试代码xxx.verilog加载进去CPU中进行运行,因为在链接阶段己经将测试代码放在ITCM中,使得程序直接在ITCM中运行,并将运行结果打印在日志文件(xxx.log)中,输出批量化验证结果如图3-4所示。
    在这里插入图片描述

    图3-4 输出批量化验证结果

      riscv-tests是能够检测指令集中的指令在处理器中运行结果是否成功,判断处理器的设计是否可以通过指令集架构定义的测试程序。在运行批量化测试结束后可以判断出指令是否执行成功:在测试程序中设定了对检测目标寄存器的判断,如果符合这意味着指令测试完成,在日志文件(xxx.log)文件中打印”PASS”字样,否则打印”FALL”。第一列PASS表示指令成功通过测试。RV32I的所有指令测试结果如表3-1所示。

    表3-1 RV32I指令集的全部47条指令的测试结果
    测试指令指令用途结果测试指令指令用途结果
    add加操作通过ld双字加载,读取八个字节通过
    addi先扩展的加操作通过lh两字节有符号位的扩展指令通过
    and与操作通过lhu两字节无符号位的扩展指令通过
    andi符号位扩展的立即数与操作通过lui高位立即数加载通过
    auipcPC加立即数指令通过lw字加载并进行位扩展通过
    beq相等跳转指令通过or或操作通过
    bge有符号大于跳转指令通过ori先扩展的或操作通过
    bgeu无符号大于跳转指令通过sbrs2低8位存储到rs1+imm通过
    blt有符号小于跳转指令通过shrs2低16位存储到rs1+imm通过
    bltu无符号小于跳转指令通过swrs2一次1个字存到rs1+imm通过
    bne数据跳转指令通过sllrs1逻辑左移rs2位通过
    csrrc读后清除控制状态寄存器通过sllirs1逻辑左移有符号imm位通过
    csrrci立即数读后设置控制状态寄存器通过slt置位操作指令通过
    csrrs读后置位控制状态寄存器通过slti置位指定操作指令通过
    csrw写控制状态寄存器指令通过sltu无符号置位操作指令通过
    csrwi立即数写控制状态寄存器指令通过sltiu无符号置位指定操作指令通过
    ebreak环境断点通过srars1算数右移imm位通过
    ecall环境调用通过srairs1逻辑右移imm位通过
    fence同步内存和I/O通过srlrs1算数右移rs2位通过
    fence_i同步指令数据流指令通过srlirs1逻辑右移rs2位通过
    jal无条件跳转指令通过sub减操作通过
    jalr无条件指定跳转指令通过xor异或操作通过
    lb有符号位的扩展指令通过xori先扩展的加操作通过
    lbu无符号位的扩展指令通过

    流水线各阶段功能仿真

      以add指令在处理器中“流动”的过程,通过add加法运算的过程观察处理器各级流水线是否正常工作。对硬件电路各部分进行波形检测,完成verilog编写的RTL级电路各模块的功能验证,基于对指令测试代码中的反汇编指令运行情况进行分析。先验证add指令可以正常工作如下图所示打印“PASS”,再输出波形图进行观察。
    在这里插入图片描述

    图3-5 add指令测试成功

    1. 取指令阶段

      取出add指令的过程在IFU取指单元中进行,该阶段主要完成PC地址的产生和读出指令编码信息,PC值根据指令类型完成自增。对应的汇编指令解析:把mhartid的值读入到a0实现取指。

    16进制指令          对应汇编指令
    f1402573          	csrr	a0,mhartid 
    
    • 1
    • 2

      图3-6为取指阶段的仿真波形,可以看到在测试中第一个时钟周期上,ifetch给ifu2biu模块发送的ifu_req_pc信号32位PC值800000134,ifu2biu模块根据PC值向biu总线发送ifu2biu_icb_cmd_addr信号所提取指令的PC值800000134,通过biu总线返回ifetch到ifu_rsp_instr信号提取了外部指令F1402573。IFU取指阶段的输出信号有三个:ifu_o_ir是当前从biu中提取的32位指令数据F1402573,ifu_o_pc是所提取指令的PC地址,inspect_pc是所提取的32位指令使得PC值+4。经测试能够正常完成第二个时钟周期PC地址自增和IFU单元取指令。
    在这里插入图片描述

    图3-6 取指阶段波形输出

    2. 译码阶段

      该阶段根据指令的低7位的操作码opcode[6:0]翻译出指令类型,然后通过3位寄存器索引cmt_dcause[2:0]从通用寄存器中读取操作数,对应的汇编指令解析:条件分支跳转指令若a0不为零则跳转8000013e。

    16进制指令          对应汇编指令
    e101             bnez     a0,8000013e 
    4181             li       gp,0
    00000297         auipc	t0,0x0
    
    • 1
    • 2
    • 3
    • 4

      图3-7为译码阶段的仿真结果,可以从图中看出,在第一个周期,ifu_rsp_instr是e101,意味着下一周期需判断是否IFU有输入;在第二个周期,dec_bjp为高电平,读出立即数dec_imm并跳转。条件分支跳转指令是16位指令下一周期IFU提取指令部分完成PC+2,ifu_req_pc从80000013C加2等于80000013E。在第三个周期,看见ifu_rsp_instr[31:0]由02974181变为00000297,与反汇编指令一致。
    在这里插入图片描述

    图3-7 执行阶段的译码部分波形输出

    3. 执行阶段

      主要完成对译码阶段后的操作数进行相关的运算,并将运算结果输出,如果是条件跳转需要先进行跳转判断,最后需要判断流水线内部是否需要对取指令阶段的PC生成模块进行流水冲刷[19]。先要打开寄存器编写模式:将8(二进制0111,经查询CSRs寄存器的状态0111代表使能)赋值给a0, 再给mstatus用于保存全局中断使能或其他的CSRs寄存器状态。然后打开mie和mtcev寄存器。操作CSR寄存器向寄存器写入数据。

    16进制指令       对应汇编指令
    ec228293          	addi	t0,t0,-318 <trap_vector>
    4521               li	a0,8
    30052073          	csrs	mstatus,a0
    fff00513          	li	a0,-1
    30452073          	csrs	mie,a0
    30529073          	csrw	mtvec,t0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

      执行阶段的仿真波形图如图3-8所示。从图中的波形可以看出执行阶段的将译码得到的操作数0x80000142与立即数0xFFFFFECC进行add指令加法运算操作,0x80000000 + 0x00000004结果为alu_req_alu_res[31:0]信号为0x80000004,与反汇编程序中的addi指令一致[11]。

    下面的表述更清晰。为了查重,我就换了个表达方式。
    
    • 1

      图3-8为执行阶段的仿真波形图,从图中的波形可以看出此阶段的操作将译码阶段输出的操作数0x80000142与立即数0xFFFFFEC2进行加法运算,结果alu_req_alu_res[31:0]信号为0x80000004(溢出保留地位有效位),与反汇编程序中的addi指令一致。
    在这里插入图片描述

    图3-8 执行阶段的波形输出

      经测试,流水线的取add指令、译码add指令、执行add指令的波形输出符合预期效果,预期产生的16位进制指令可以从波形图中找到对应的指令

  • 相关阅读:
    vue基础知识十:Vue中组件和插件有什么区别?
    Vue中的数据监视
    根据输入类型来选择函数不同的实现方法functools.singledispatch
    从源码层面深度剖析Spring循环依赖
    JDK8 — 17特性
    从 Windows 切换到 Mac,这些不能错过的 Tips
    vulnhub靶场之DIGITALWORLD.LOCAL: VENGEANCE
    Node如何获取pnpm安装的包源码真实代码路径并操作
    Python实现深度森林(Deep Forest)分类模型(deepforest分类算法)项目实战
    Web开发小妙招:巧用ThreadLocal规避层层传值
  • 原文地址:https://blog.csdn.net/qq_43858116/article/details/125516161