一、实验目标
了解RISC-V mini处理器架构,在其基础之上新增一个指令,完成设计并观察指令执⾏。
二、实验内容
-
修改数据通路,新增指令comb rs1,rs2,rd采用R型指令格式,实现将rs1高16位和rs2低16位拼接成32位整数,并且保存到rd寄存器。
-
在处理器上执行该指令,观察仿真波形,验证功能是否正确。
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指令的比特模式:
- // Instructions.scala
- package min
- import chisel3.util.BitPat
- object Instructions {
- / / 省略RISCV-mini已定义的指令
- // 新增指令COMB
- def COMB = BitPat("b0000001??????????111?????0110011")
- }
这里的BitPat()函数用于定义一个位模式,确保与现有指令无冲突,其中?表示不关心的位,可以是0或1。
2. 添加 comb 指令的译码
步骤 2.1:定义 ALU 操作常量
comb 指令需要在ALU中将rs1高16位和rs2低16位拼接成32位整数,因此需要在Alu.scala文件中添加常量 ALU_COMB ,让译码器可以译码出正确的信号。因此,在 Alu.scala 文件中添加新的操作码:
- // Alu.scala
- import chisel3._
- import chisel3.util._
- object Alu {
- val ALU_ADD = 0.U(4.W)
- val ALU_SUB = 1.U(4.W)
- val ALU_AND = 2.U(4.W)
- val ALU_OR = 3.U(4.W)
- val ALU_XOR = 4.U(4.W)
- val ALU_SLT = 5.U(4.W)
- val ALU_SLL = 6.U(4.W)
- val ALU_SLTU = 7.U(4.W)
- val ALU_SRL = 8.U(4.W)
- val ALU_SRA = 9.U(4.W)
- val ALU_COPY_A = 10.U(4.W)
- val ALU_COPY_B = 11.U(4.W)
- // 新加常量
- val ALU_COMB = 12.U(4.W)
- val ALU_XXX = 15.U(4.W)
- }
- //省略RISCV-mini的ALU实现部分
步骤 2.2:设置控制信号映射
接下来为 comb 指令添加对应的译码映射。 comb 指令执行后pc需要加4,并将从寄存器文件中读取的数据rs1和rs2进行拼接操作,然后将ALU输出的拼接结果写回到寄存器文件中。在 Control.scala 文件中为 comb 指令配置控制信号:
- // Control.scala
- import chisel3._
- import chisel3.util._
- object Control {
- //省略常量定义部分
- // format: off
- val default =
- List(PC_4 , A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, Y)
- val map = Array(
- LUI -> List(PC_4 , A_PC, B_IMM, IMM_U, ALU_COPY_B, BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
- AUIPC -> List(PC_4 , A_PC, B_IMM, IMM_U, ALU_ADD , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
- JAL -> List(PC_ALU, A_PC, B_IMM, IMM_J, ALU_ADD , BR_XXX, Y, ST_XXX, LD_XXX, WB_PC4, Y, CSR.N, N),
- //省略部分指令译码映射
- // 这是COMB指令的译码映射
- COMB -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_COMB , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N))
- // format: on
- }
- //省略RISCV-mini的控制实现部分
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 位数据。
- // Alu.scala
- import chisel3._
- import chisel3.util._
- //省略Alu常量定义和端口声明部分
- class AluSimple(val width: Int) extends Alu {
- val io = IO(new AluIO(width))
- val shamt = io.B(4, 0).asUInt
- io.out := MuxLookup(
- io.alu_op,
- io.B,
- Seq(
- ALU_ADD -> (io.A + io.B),
- ALU_SUB -> (io.A - io.B),
- ALU_SRA -> (io.A.asSInt >> shamt).asUInt,
- ALU_SRL -> (io.A >> shamt),
- ALU_SLL -> (io.A << shamt),
- ALU_SLT -> (io.A.asSInt < io.B.asSInt),
- ALU_SLTU -> (io.A < io.B),
- ALU_AND -> (io.A & io.B),
- ALU_OR -> (io.A | io.B),
- ALU_XOR -> (io.A ^ io.B),
- ALU_COPY_A -> io.A,
- // COMB指令执行
- ALU_COMB -> Cat(io.A(31,16), io.B(15,0))
- )
- )
- io.sum := io.A + Mux(io.alu_op(0), -io.B, io.B)
- }
- class AluArea(val width: Int) extends Alu {
- val io = IO(new AluIO(width))
- val sum = io.A + Mux(io.alu_op(0), -io.B, io.B)
- val cmp =
- Mux(io.A(width - 1) === io.B(width - 1), sum(width - 1), Mux(io.alu_op(1), io.B(width - 1), io.A(width -1)))
- val shamt = io.B(4, 0).asUInt
- val shin = Mux(io.alu_op(3), io.A, Reverse(io.A))
- val shiftr = (Cat(io.alu_op(0) && shin(width - 1), shin).asSInt >> shamt)(width - 1, 0)
- val shiftl = Reverse(shiftr)
- // 将A(rs1)的高16位与B(rs2)的低16位拼接
- val comb = Cat(io.A(31,16), io.B(15,0))
- val out =
- Mux(
- io.alu_op === ALU_ADD || io.alu_op === ALU_SUB,
- sum,
- Mux(
- io.alu_op === ALU_SLT || io.alu_op === ALU_SLTU,
- cmp,
- Mux(
- io.alu_op === ALU_SRA || io.alu_op === ALU_SRL,
- shiftr,
- Mux(
- io.alu_op === ALU_SLL,
- shiftl,
- Mux(
- io.alu_op === ALU_AND,
- io.A & io.B,
- Mux(
- io.alu_op === ALU_OR,
- io.A | io.B,
- Mux(io.alu_op === ALU_XOR, io.A ^ io.B,
- // COMB指令执行
- Mux(io.alu_op === ALU_COMB, comb, Mux(io.alu_op===ALU_COPY_A, io.A, io.B)))
- )
- )
- )
- )
- )
- )
- io.out := out
- io.sum := sum
- }
对 comb 指令进行测试
步骤 4.1:编写测试程序
编写一个程序加载到处理器中,调用 comb 指令并观察结果。comb.s代码如下:
- .text # Define beginning of text section
- .global _start # Define entry _start
- _start:
- lui x6, 1 # x6 = 0x00001000
- lui x7, 2 # x7 = 0x00002000
- # comb x5, x6, x7
- exit:
- csrw mtohost, 1
- j exit
- .end # End of file
请注意,因为 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 的设置为:
- opcode: 0110011
- funct3: 110
- funct7: 0000001
在 Instructions.scala 文件中添加以下内容:
- // Instructions.scala
- import chisel3.util.BitPat
- object Instructions {
- // 已有指令省略
- def NEW_INST = BitPat("b0000001??????????110?????0110011")
- }
2. 添加 comb 指令的译码
步骤 2.1:定义 ALU 操作常量
在 Alu.scala 文件中为 new_inst 指令添加常量:
- // Alu.scala
- object Alu {
- // 已有常量省略
- val ALU_NEW_INST = 13.U(4.W) // 新指令对应的 ALU 操作常量
- }
步骤 2.2:设置控制信号映射
修改 Control.scala 文件,设置指令控制信号。为 new_inst 指令添加控制信号映射,确保译码后能正确触发对应的 ALU 操作:
- // Control.scala
- val map = Array(
- // 已有映射省略
- NEW_INST -> List(PC_4, A_RS1, B_RS2, IMM_X, ALU_NEW_INST, BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N)
- )
3. 实现 new_inst 指令的执行操作
步骤 3.1:实现指令逻辑
为 new_inst 添加操作(~io.A & io.B 表示对 rs1 的值取反后与 rs2 进行按位与操作):
- // Alu.scala
- class AluSimple(val width: Int) extends Alu {
- val io = IO(new AluIO(width))
- io.out := MuxLookup(
- io.alu_op,
- io.B,
- Seq(
- // 已有操作省略
- ALU_NEW_INST -> (~io.A & io.B) // 新指令逻辑实现
- )
- )
- }
要在 AluArea 中添加对 new_inst 指令的支持,进行以下步骤:
- 定义指令逻辑:new_inst 的功能是对 rs1 取反后与 rs2 按位与,将结果存储到 rd。对应到硬件逻辑,可以直接通过 ~io.A & io.B 实现。
- 在ALU的Mux逻辑中插入新指令处理逻辑:在out的生成逻辑中添加new_inst的逻辑。
- 确保ALU操作码正确映射:在控制逻辑中定义ALU_NEW_INST并在AluArea中使用。
- class AluArea(val width: Int) extends Alu {
- val io = IO(new AluIO(width))
- val sum = io.A + Mux(io.alu_op(0), -io.B, io.B)
- val cmp = Mux(
- io.A(width - 1) === io.B(width - 1),
- sum(width - 1),
- Mux(io.alu_op(1), io.B(width - 1), io.A(width - 1))
- )
- val shamt = io.B(4, 0).asUInt
- val shin = Mux(io.alu_op(3), io.A, Reverse(io.A))
- val shiftr = (Cat(io.alu_op(0) && shin(width - 1), shin).asSInt >> shamt)(width - 1, 0)
- val shiftl = Reverse(shiftr)
- // 新增指令逻辑: rs1取反后与rs2按位与
- val newInst = ~io.A & io.B
- // 其他指令逻辑
- val comb = Cat(io.A(31, 16), io.B(15, 0))
- val out =
- Mux(
- io.alu_op === ALU_ADD || io.alu_op === ALU_SUB,
- sum,
- Mux(
- io.alu_op === ALU_SLT || io.alu_op === ALU_SLTU,
- cmp,
- Mux(
- io.alu_op === ALU_SRA || io.alu_op === ALU_SRL,
- shiftr,
- Mux(
- io.alu_op === ALU_SLL,
- shiftl,
- Mux(
- io.alu_op === ALU_AND,
- io.A & io.B,
- Mux(
- io.alu_op === ALU_OR,
- io.A | io.B,
- Mux(
- io.alu_op === ALU_XOR,
- io.A ^ io.B,
- Mux(
- io.alu_op === ALU_COMB,
- comb,
- Mux(
- io.alu_op === ALU_NEW_INST, // 新增指令的逻辑
- newInst,
- Mux(io.alu_op === ALU_COPY_A, io.A, io.B)
- )
- )
- )
- )
- )
- )
- )
- )
- )
- io.out := out
- io.sum := sum
- }
4. 对 new_inst 指令进行测试
步骤 4.1:编写测试程序
编写一个程序加载到处理器中,调用 new_inst 指令并观察结果。new_inst.s代码如下:
- .text # Define beginning of text section
- .global _start # Define entry _start
- _start:
- lui x6, 1 # x6 = 0x00001000
- lui x7, 2 # x7 = 0x00002000
- # new_inst x5, x6, x7
- exit:
- csrw mtohost, 1
- j exit
- .end # End of file
因为 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 |
手算验证:
- ~x6 = 0xFFFFEFFF 的二进制表示为:1111 1111 1111 1110 1111 1111 1111 1111。
- x7 = 0x00002000 的二进制表示为:0000 0000 0000 0010 0000 0000 0000 0000。
- 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 = 0x00001000 的二进制表示为:00000000 00000001 00000000 00000000。
- x7 = 0x00002000 的二进制表示为:00000000 00000010 00000000 00000000。
将 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 中。
- x6 = 0x00001000 的二进制表示为:00000000 00000001 00000000 00000000。
- 对 x6 进行按位取反(~x6),得到 0xFFFFEFFF,即二进制表示为:11111111 11111111 11101111 11111111。
- x7 = 0x00002000 的二进制表示为:00000000 00000010 00000000 00000000。
- 按位与操作 ~x6 与 x7:
- 11111111 11111111 11101111 11111111 (x6取反)
- AND
- 00000000 00000010 00000000 00000000 (x7)
-
- 00000000 00000010 00000000 00000000 (结果)
得到的结果是 0x00002000,所以 x5 的值为 0x00002000,指令执行正确。