基于五阶段流水线的RISC-V CPU模拟器实现 (5)

此外,模拟器还支持单步调试和verbose输出的功能,使用-s和-v参数即可开启单步调试模式。使用-v参数并重定向标准输出可以得到寄存器状态和流水线状态的完整执行历史并在事后进行分析。一条典型的CPU执行状态记录如下

Fetched instruction 0x00000593 at address 0x100c4 Decoded instruction 0x40a60633 as sub a2,a2,a0 Execute: addi Forward Data a2 to Decode op1 Memory Access: addi Forward Data a0 to Decode op2 WriteBack: addi ------------ CPU STATE ------------ PC: 0x100c8 zero: 0x00000000(0) ra: 0x00000000(0) sp: 0x80000000(2147483648) gp: 0x00011f58(73560) tp: 0x00000000(0) t0: 0x00000000(0) t1: 0x00000000(0) t2: 0x00000000(0) s0: 0x00000000(0) s1: 0x00000000(0) a0: 0x00000000(0) a1: 0x00000000(0) a2: 0x00000000(0) a3: 0x00000000(0) a4: 0x00000000(0) a5: 0x00000000(0) a6: 0x00000000(0) a7: 0x00000000(0) s2: 0x00000000(0) s3: 0x00000000(0) s4: 0x00000000(0) s5: 0x00000000(0) s6: 0x00000000(0) s7: 0x00000000(0) s8: 0x00000000(0) s9: 0x00000000(0) s10: 0x00000000(0) s11: 0x00000000(0) t3: 0x00000000(0) t4: 0x00000000(0) t5: 0x00000000(0) t6: 0x00000000(0) ----------------------------------- 3.7 实现中遇到的坑

在整个实现中,我在第一阶段的单周期指令级模拟的实现并没有遇到什么问题,但是流水线相关的模拟中,遇到了几个相当微妙的错误。

一个根本的困难在于我们对流水线的模拟程序本质上还是线性执行的,并不能像硬件那样多阶段并行执行。因此,必须非常小心地设计五个阶段的代码的执行流和对数据结构的访问,才能模拟出硬件的效果。

当多个阶段发现数据冒险并向前转发数据时,必须优先传送更新的数据。在模拟器中,由于相关阶段的执行顺序是执行->访存->写回,因此会存在前面的阶段向前转发的数据被后面的阶段的旧数据覆盖的可能。对于这种情况,模拟器中必须加以特别的判定。

分支预测模块应当在解码阶段根据预测结果修改PC的值,但是,如果这个跳转指令是被错误取进来,并且应该在之后被Bubble的话怎么办?必须想办法恢复被修改的PC值,或者延迟写入预测的PC值。

也是由于代码是顺序执行的,因此当执行阶段发现访存指令,而解码阶段的指令依赖访存数据并导致内存冒险时,必须非常小心地设计整个执行过程和数据访问流程,才能模拟出正确的结果。

用于系统调用的ecall指令也会导致数据冒险!并且产生数据冒险的条目,取决于这个系统调用的参数数量和其对应的寄存器!当前的系统调用会依赖的寄存器有a0和a7两个,因此刚好能作为op1和op2塞入流水线,但是如果系统调用需要的参数更多,实现将会变得更为复杂。

zero寄存器是一个相当独特的存在,理论上他任何时候值应该都是0,所以进行数据转发的时候必须处处特判零寄存器,如果向零寄存器里的值进行数据转发就会导致非常难以发现的错误。

四、功能测试与性能评测 4.1 模拟器的功能正确性测试

我自己编写的测试程序见下表,注意所有的程序都需要和test/lib.c一起编译。

代码文件 对应的ELF文件
test/helloworld.c   riscv-elf/helloworld.riscv  
test/test_arithmetic.c   riscv-elf/test_arithmetic.riscv  
test/test_syscall.c   riscv-elf/test_syscall.riscv  
test/test_branch.c   riscv-elf/test_branch.riscv  
test/quicksort.c   riscv-elf/quicksort.riscv  
test/matrixmulti.c   riscv-elf/matrixmulti.riscv  
test/ackermann.c   riscv-elf/ackermann.riscv  

每个代码文件的功能描述如下

代码文件 功能描述
test/helloworld.c   最简单的Hello, World  
test/test_arithmetic.c   测试一组算术运算  
test/test_syscall.c   测试全部的系统调用  
test/test_branch.c   测试条件和循环语句  
test/quicksort.c   分别对10和100个元素进行快速排序  
test/matrixmulti.c   10*10矩阵乘法  
test/ackermann.c   求解一组Ackermann函数的值  

如果模拟器程序Simulator在项目中的build/目录下,可以运行如下命令,得到运行结果,来验证模拟器的正确性。注意test_syscall.riscv程序中存在用户输入的部分。

./Simulator ../riscv-elf/helloworld.riscv ./Simulator ../riscv-elf/test_arithmetic.riscv ./Simulator ../riscv-elf/test_syscall.riscv ./Simulator ../riscv-elf/test_branch.riscv ./Simulator ../riscv-elf/quicksort.riscv ./Simulator ../riscv-elf/matrixmulti.riscv ./Simulator ../riscv-elf/ackermann.riscv

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyfxjz.html