通过RTFM初步了解RISC-V指令集
查阅RISC-V手册的目录, 你发现RV32I在哪一章进行介绍? 尝试在该章节中查阅RV32I的相关内容, 回答下列问题:
- PC寄存器的位宽是多少?
- GPR共有多少个? 每个GPR的位宽是多少?
R[0]和sISA的R[0]有什么不同之处?- 指令编码的位宽是多少? 指令有多少种基本格式?
- 在指令的基本格式中, 需要多少位来表示一个GPR? 为什么?
add指令的格式具体是什么?- 还有一种基础指令集称为RV32E, 它和RV32I有什么不同?
RV32I在第2章RV32I Base Integer Instruction Set, Version 2.1进行介绍:
- PC寄存器的位宽是32.
- GPR共有31个(
x1-x31),每个GPR的位宽为32. - RV32I中的
R[0]被硬连线为全0,而sISA中R[0]则为通用寄存器. - 指令编码的位宽为32.指令有6种基本格式(R/I/S/B/U/J).
- RV32I基本格式中至少需要4位来表示一个GPR,因为RV32I选择了32个整数寄存器的配置.
add指令的具体格式为:
1 | 31 25 24 20 19 15 14 12 11 7 6 0 |
- RV32E为为资源受限的嵌入式设备设计的RV32I简化版本,唯一的变化是将整数寄存器数量减少到16个。
实现两条指令的minirv处理器
理解addi和jalr指令的功能后, 根据你之前设计sISA处理器的经验, 尝试设计一个支持这两条RISC-V指令的处理器.
由于GPR需要进行较多的连线工作, 为了减轻大家的负担, 我们准备了一个预先完成大量连线工作的GPR子模块. 下载后, 通过Logisim打开文件GPR.circ, 即可看到GPR的电路设计, 你可以整体选择这个电路后, 通过复制和粘贴将其加入到你工程中. 不过, 这个电路并没有实现GPR的完整功能, 你需要根据你对GPR的理解完善它.
1 | 00000000 <_start>: |
尝试通过指令集的状态机理解这个程序的功能. 理解后, 将程序其放置在ROM中, 并尝试运行你的处理器, 然后检查处理器的运行结果是否符合预期.
GPR模块的连线如下:

译码模块如下:

miniCPU的整体结构如下:

测试addi指令
在上述测试程序中, addi指令的立即数比较小. 为了测试符号扩展的实现是否正确, 你需要让处理器执行一些立即数为负数的addi指令. 尝试编写若干条这种类型的addi指令, 并放置到ROM中, 检查你的实现是否正确.
测试以下指令:
1 | 0: 0x300093 addi a0, zero, 3 |
运行后,cpu的x1寄存器的值应该为2,如图所示:

实现完整的minirv处理器
实现add和lui指令. 实现后, 尝试编写一些简单的指令序列放置到ROM中, 来初步检查你的实现是否正确.
改动后的译码器如下:

整体结构如下:

使用如下程序进行测试:
1 | 0: 0x000010b7 lui x1, 1 |
运行后,cpu的x1寄存器的值应该为3,x2寄存器的值应为2,如图所示:

实现完整的minirv处理器(2)
实现lw和sw指令, 然后编写一些简单的指令序列放置到ROM中, 来初步检查你的实现是否正确. 特别地, 你可以用鼠标右键点击RAM组件, 然后通过Edit Contents在RAM中放置一些数据, 来帮助你测试访存指令的行为.
因为yxys文档中说明了不考虑内存未对齐的情况,因此可以直接使用指令地址的高30位来作为访存地址。
增加lw和sw指令支持后的译码器和cpu结构图如下:


首先编辑RAM中地址0x1为0xaa,然后使用如下程序进行测试:
1 | 0: 0x00402083 lw x1, 0x100(x0) |
程序执行后,RAM中0x1和0x0都应该存储0xaa,如图所示:

实现完整的minirv处理器(3)
实现lbu指令, 并通过一些指令序列来初步检查你的实现是否正确.
Hint: 你可以先在RAM中放置一个4字节的数据0x12345678, 并通过lw指令读出它(假设数据位于内存地址a), 确认读出结果为0x12345678. 然后通过若干条lbu指令分别从内存地址a, a+1, a+2, a+3中读出数据, 我们预期这些lbu指令分别读出0x78(对应地址a), 0x56, 0x34, 0x12(对应地址a+3).
增加lbu指令后的译码器和CPU结构如下:


我们先将RAM中0x0地址设为0x12345678,然后使用lw指令将0x0的数据存入x1,然后将0x0、0x1、0x2、0x3的但单字节分别存到x2、x3、x4、x5,测试程序如下:
1 | 0: 0x00402083 lw x1, 0x100(x0) |
测试结果如下,可以看到数据被正确的放入对应寄存器:

实现完整的minirv处理器(4)
实现sb指令, 并通过一些指令序列来初步检查你的实现是否正确.
Hint: 你可以先在RAM中放置一个4字节的数据0x12345678, 并通过lw指令读出它(假设数据位于内存地址a), 确认读出结果为0x12345678. 然后通过若干条sb指令分别往内存地址a+3, a+2, a+1, a+0 中写入0x90(对应地址a+3), 0xab, 0xcd, 0xef(对应地址a); 写入之前, 可以通过addi指令配合零号寄存器, 来向目的寄存器写入一个立即数, 从而实现sISA中li指令的效果. 最后再次通过lw指令读出新数据, 我们预期读出结果为0x90abcdef.
增加对SB指令支持后的译码器结构图和CPU结构图如下所示:


测试程序如下:
1 | 0: 0x00402083 lw x1, 0x100(x0) |
测试结果如下,可以看到RAM中地址为0x2的字被正确写入0x12345678。

在minirv处理器上执行C程序
分别加载并运行mem.hex和sum.hex. 运行指定时间后, 检查处理器的状态, 若PC位于halt函数附近, 且a0寄存器为0, 则说明程序运行正确. 两个程序的预期运行时间如下:
mem.hex- 6000周期sum.hex- 6000周期
如果你发现运行指定时间后, PC位于其他位置, 或a0寄存器不为0, 则说明程序运行错误. 但由于这个过程中已经运行了上千条指令, 很难发现是哪一条指令执行出错, 因此我们还是推荐你做好上一道必做题的验证工作, 通过一些简单的指令序列来检查你的处理器实现是否正确.
a0寄存器对应x10,执行程序的cpu结构图如下:

以mem.hex为例,执行结果如下,可以看到PC停在0x1218处,即halt函数中,以及寄存器x10的值为0:


为minirv处理器添加图形显示功能
在处理器的数据通路上添加RGB Video组件. 我们约定, 屏幕像素对应的存储区域是[0x20000000, 0x20040000).添加组件后, 加载并运行vga.hex程序.
vgaCPU结构如下所图所示:

Logo绘制如下:

F阶段堂堂完结
YSYX To Be Continue…