一、实验目标
了解RISC-V mini处理器架构,在其基础之上新增一个指令,完成设计并观察指令执⾏。
二、实验内容
1) 修改数据通路,新增指令comb rs1,rs2,rd采用R型指令格式,实现将rs1高16位和rs2低16位拼接成32位整数,并且保存到rd寄存器。
2) 在处理器上执行该指令,观察仿真波形,验证功能是否正确。
3) 自行设计其他功能指令,并验证设计是否正确
三、实验环境
硬件:桌面PC
软件:Chisel开发环境
四、实验步骤及说明
1) 实验要求
学习Chisel数据通路的Chisel描述,特别是指令译码部分和core核心代码。然后按照下面操作完成指令译码器的修改,以及数据通路的修改,按照参考文档完成comb指令的实现,自行设计新指令实现其功能并验证。
2) 实验过程
(一)COMB指令
分析:添加新指令 comb ,首先需要根据riscv指令格式,设置该指令各个字段的值,并在相应文件中添加该指令的比特模式。然后设置该指令的译码结果,接着在ALU中实现该指令的功能。最后让该指令在处理器上执行,验证功能是否正确。
1. 在Instrutcions.scala文件中添加 comb 指令比特模式串
comb 为R型指令,riscv的R型指令格式如下:
指令功能:comb 是一种 R 型指令,将 rs1 的高 16 位与 rs2 的低 16 位拼接成 32 位数据,结果存储到 rd。
指令格式:基于 RISC-V 的 R 型指令格式,具体字段如下:
opcode(操作码):7位,用于指定指令类型。
rs2(源寄存器2):5位,指定第二个源寄存器。
rs1(源寄存器1):5位,指定第一个源寄存器。
funct3(功能字段3):3位,用于区分具有相同操作码的不同指令。
rd(目标寄存器):5位,指定目标寄存器,用于存放指令执行结果。
opcode/funct7(功能字段7):7位,用于区分具有相同操作码和funct3的不同指令。
为了避免新加指令与riscv-mini已有指令冲突,将 comb 指令的opcode、funct3和funct7部分设置为0110011、111、0000001。然后使用 BitPat() 函数设置 comb 指令的比特模式。
步骤 1.1:定义指令比特模式
在Instructions.scala文件中,添加以下的代码来定义comb指令的比特模式:
这里的BitPat()函数用于定义一个位模式,确保与现有指令无冲突,其中?表示不关心的位,可以是0或1。
2. 添加 comb 指令的译码
步骤 2.1:定义 ALU 操作常量
comb 指令需要在ALU中将rs1高16位和rs2低16位拼接成32位整数,因此需要在Alu.scala文件中添加常量 ALU_COMB ,让译码器可以译码出正确的信号。因此,在 Alu.scala 文件中添加新的操作码:
步骤 2.2:设置控制信号映射
接下来为 comb 指令添加对应的译码映射。 comb 指令执行后pc需要加4,并将从寄存器文件中读取的数据rs1和rs2进行拼接操作,然后将ALU输出的拼接结果写回到寄存器文件中。在 Control.scala 文件中为 comb 指令配置控制信号:
3. 实现 comb 指令的执行操作
步骤 3.1:实现拼接逻辑
在Alu.scala文件添加将rs1高16位和rs2低16位拼接成32位整数的操作。因此,在 Alu.scala 文件中为 comb 指令添加具体执行逻辑:使用 MuxLookup 多路选择器,根据 alu_op 确定执行操作。Cat() 函数将 io.A(31,16) 与 io.B(15,0) 拼接为 32 位数据。
对 comb 指令进行测试
步骤 4.1:编写测试程序
编写一个程序加载到处理器中,调用 comb 指令并观察结果。comb.s代码如下:
请注意,因为 comb 为自己加入的指令,不能被汇编器汇编,所以这里将其注释掉,到后面生成的comb.hex文件中再将 comb x5, x6, x7 的二进制添加进去。
步骤 4.2:编译测试程序
编写完程序后,使用如下命令进行编译:
$ riscv32-unknown-elf-gcc -nostdlib -Ttext=0x200 -o comb comb.s
然后使用 elf2hx 命令将comb二进制文件转换成十六进制:
$ elf2hex 16 4096 comb > comb.hex
在comb.hex文件中,可以找到 lui x6, 1 和 lui x7, 2 的机器码对应的十六进制形式:
comb x5, x6, x7 转换成机器码的十六机制形式为 027372b3。因此处指令存储为小端模式,故我们需要将十六进制数插入到第一个红线的前面。修改后如下:
步骤 4.3:运行仿真并观察波形
接着需要在主目录下一次执行 make 和 make verilator 命令(若之前已经执行过,则在此次操作之前需要执行 make clean ),执行后会产生VTile可执行文件。然后执行下面命令,使mini处理器执行新建指令并产生波形文件。
$ ./VTile comb.hex comb.vcd
使用GTKWave打开comb.vcd文件,其波形图如下:
指令对应的十六进制形式见下表:
表 1:指令所对应16进制
指令 |
十六进制形式 |
说明 |
lui x6,1 |
00001337 |
x6=0x00001000 |
lui x7,2 |
000023b7 |
x7=0x00002000 |
comb x5,x6,x7 |
027372b3 |
x5=cat(x6(31:16),x7(15:0))s |
从波形图中可以看出, comb 指令将拼接后的结果0x00002000写回到了5号寄存器中,故该指令执行正常。
(二)new_inst指令
添加新指令 new_inst ,new_inst 是一个 R 型指令,功能为:将 rs1 和 rs2 的值进行对 rs1 的值取反后与 rs2 进行按位与操作,将结果存储到 rd。
1. 在Instrutcions.scala文件中添加 new_inst 指令比特模式串
指令格式:
基于 R 型指令格式,opcode、funct3 和 funct7 的设置为:
在 Instructions.scala 文件中添加以下内容:
2. 添加 comb 指令的译码
步骤 2.1:定义 ALU 操作常量
在 Alu.scala 文件中为 new_inst 指令添加常量:
步骤 2.2:设置控制信号映射
修改 Control.scala 文件,设置指令控制信号。为 new_inst 指令添加控制信号映射,确保译码后能正确触发对应的 ALU 操作:
3. 实现 new_inst 指令的执行操作
步骤 3.1:实现指令逻辑
为 new_inst 添加操作(~io.A & io.B 表示对 rs1 的值取反后与 rs2 进行按位与操作):
要在 AluArea 中添加对 new_inst 指令的支持,进行以下步骤:
4. 对 new_inst 指令进行测试
步骤 4.1:编写测试程序
编写一个程序加载到处理器中,调用 new_inst 指令并观察结果。new_inst.s代码如下:
因为 new_inst 为自己加入的指令,不能被汇编器汇编,所以这里将其注释掉,到后面生成的new_inst.hex文件中再将 new_inst x5, x6, x7 的二进制添加进去。
步骤 4.2:编译测试程序
编写完程序后,使用如下命令进行编译:
$ riscv32-unknown-elf-gcc -nostdlib -Ttext=0x200 -o new_inst new_inst.s
然后使用 elf2hx 命令将new_inst二进制文件转换成十六进制:
$ elf2hex 16 4096 new_inst >new_inst.hex
在new_inst.hex文件中,可以找到 lui x6, 1 和 lui x7, 2 的机器码对应的十六进制形式:
new_inst x5, x6, x7 转换成机器码的十六机制形式为 01070333。因此处指令存储为小端模式,故我们需要将十六进制数插入到第一个红线的前面。修改后如下:
步骤 4.3:运行仿真并观察波形
接着需要在主目录下一次执行 make 和 make verilator 命令(若之前已经执行过,则在此次操作之前需要执行 make clean ),执行后会产生VTile可执行文件。然后执行下面命令,使mini处理器执行新建指令并产生波形文件。
$ ./VTile new_inst.hex new_inst.vcd
使用GTKWave打开new_inst.vcd文件,其波形图如下:
指令对应的十六进制形式见下表:
表 2:指令所对应16进制
指令 |
十六进制形式 |
说明 |
lui x6,1 |
00001337 |
x6=0x00001000 |
lui x7,2 |
000023b7 |
x7=0x00002000 |
new_inst x5,x6,x7 |
01070333 |
x5=~(x6)&x7=0x00002000 |
手算验证:
从波形图中可以看出, new_inst 指令将结果0x00002000写回到了5号寄存器中,故该指令执行正常。
五、实验结果
(一)COMB指令
指令对应的十六进制形式见下表:
指令 |
十六进制形式 |
说明 |
lui x6,1 |
00001337 |
x6=0x00001000 |
lui x7,2 |
000023b7 |
x7=0x00002000 |
comb x5,x6,x7 |
027372b3 |
x5=cat(x6(31:16),x7(15:0)) |
从波形图中可以看出, comb 指令将拼接后的结果0x00002000写回到了5号寄存器中,故该指令执行正常。
结果验证:comb 指令的功能是将两个寄存器的值拼接成一个 32 位的整数。具体来说,comb 指令将 x6 的高 16 位和 x7 的低 16 位拼接在一起,形成一个新的 32 位值,并将结果写入 x5 寄存器。
将 x6 的高 16 位(00000000 00000001)与 x7 的低 16 位(00000000 00000000)拼接,得到的 32 位结果是:
x5 = cat(x6(31:16), x7(15:0)) = 0x00002000
因此,x5 的最终值是 0x00002000,指令执行正确。
(二)new_inst指令
指令对应的十六进制形式见下表:
指令 |
十六进制形式 |
说明 |
lui x6,1 |
00001337 |
x6=0x00001000 |
lui x7,2 |
000023b7 |
x7=0x00002000 |
new_inst x5,x6,x7 |
01070333 |
x5=~(x6)&x7=0x00002000 |
从波形图中可以看出, new_inst 指令将结果0x00002000写回到了5号寄存器中,故该指令执行正常。
结果分析:new_inst 是一个 R 型指令,的功能是将 x6 的值按位取反后,与 x7 的值进行按位与操作,并将结果存储到 x5 中。
得到的结果是 0x00002000,所以 x5 的值为 0x00002000,指令执行正确。