计算机组成原理(计算机系统3)--实验七:新增指令实验

一、实验目标

了解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指令的比特模式:

  1. // Instructions.scala
  2. package min
  3. import chisel3.util.BitPat
  4. object Instructions {
  5. / / 省略RISCV-mini已定义的指令
  6. // 新增指令COMB
  7. def COMB = BitPat("b0000001??????????111?????0110011")
  8. }

这里的BitPat()函数用于定义一个位模式,确保与现有指令无冲突,其中?表示不关心的位,可以是0或1。

2. 添加 comb 指令的译码

步骤 2.1:定义 ALU 操作常量

comb 指令需要在ALU中将rs1高16位和rs2低16位拼接成32位整数,因此需要在Alu.scala文件中添加常量 ALU_COMB ,让译码器可以译码出正确的信号。因此,在 Alu.scala 文件中添加新的操作码:

  1. // Alu.scala
  2. import chisel3._
  3. import chisel3.util._
  4. object Alu {
  5. val ALU_ADD = 0.U(4.W)
  6. val ALU_SUB = 1.U(4.W)
  7. val ALU_AND = 2.U(4.W)
  8. val ALU_OR = 3.U(4.W)
  9. val ALU_XOR = 4.U(4.W)
  10. val ALU_SLT = 5.U(4.W)
  11. val ALU_SLL = 6.U(4.W)
  12. val ALU_SLTU = 7.U(4.W)
  13. val ALU_SRL = 8.U(4.W)
  14. val ALU_SRA = 9.U(4.W)
  15. val ALU_COPY_A = 10.U(4.W)
  16. val ALU_COPY_B = 11.U(4.W)
  17. // 新加常量
  18. val ALU_COMB = 12.U(4.W)
  19. val ALU_XXX = 15.U(4.W)
  20. }
  21. //省略RISCV-mini的ALU实现部分

步骤 2.2:设置控制信号映射

接下来为 comb 指令添加对应的译码映射。 comb 指令执行后pc需要加4,并将从寄存器文件中读取的数据rs1和rs2进行拼接操作,然后将ALU输出的拼接结果写回到寄存器文件中。在 Control.scala 文件中为 comb 指令配置控制信号:

  1. // Control.scala
  2. import chisel3._
  3. import chisel3.util._
  4. object Control {
  5. //省略常量定义部分
  6. // format: off
  7. val default =
  8. List(PC_4 , A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, Y)
  9. val map = Array(
  10. 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),
  11. 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),
  12. 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),
  13. //省略部分指令译码映射
  14. // 这是COMB指令的译码映射
  15. 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))
  16. // format: on
  17. }
  18. //省略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 位数据。

  1. // Alu.scala
  2. import chisel3._
  3. import chisel3.util._
  4. //省略Alu常量定义和端口声明部分
  5. class AluSimple(val width: Int) extends Alu {
  6. val io = IO(new AluIO(width))
  7. val shamt = io.B(4, 0).asUInt
  8. io.out := MuxLookup(
  9. io.alu_op,
  10. io.B,
  11. Seq(
  12. ALU_ADD -> (io.A + io.B),
  13. ALU_SUB -> (io.A - io.B),
  14. ALU_SRA -> (io.A.asSInt >> shamt).asUInt,
  15. ALU_SRL -> (io.A >> shamt),
  16. ALU_SLL -> (io.A << shamt),
  17. ALU_SLT -> (io.A.asSInt < io.B.asSInt),
  18. ALU_SLTU -> (io.A < io.B),
  19. ALU_AND -> (io.A & io.B),
  20. ALU_OR -> (io.A | io.B),
  21. ALU_XOR -> (io.A ^ io.B),
  22. ALU_COPY_A -> io.A,
  23. // COMB指令执行
  24. ALU_COMB -> Cat(io.A(31,16), io.B(15,0))
  25. )
  26. )
  27. io.sum := io.A + Mux(io.alu_op(0), -io.B, io.B)
  28. }
  29. class AluArea(val width: Int) extends Alu {
  30. val io = IO(new AluIO(width))
  31. val sum = io.A + Mux(io.alu_op(0), -io.B, io.B)
  32. val cmp =
  33. 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)))
  34. val shamt = io.B(4, 0).asUInt
  35. val shin = Mux(io.alu_op(3), io.A, Reverse(io.A))
  36. val shiftr = (Cat(io.alu_op(0) && shin(width - 1), shin).asSInt >> shamt)(width - 1, 0)
  37. val shiftl = Reverse(shiftr)
  38. // 将A(rs1)的高16位与B(rs2)的低16位拼接
  39. val comb = Cat(io.A(31,16), io.B(15,0))
  40. val out =
  41. Mux(
  42. io.alu_op === ALU_ADD || io.alu_op === ALU_SUB,
  43. sum,
  44. Mux(
  45. io.alu_op === ALU_SLT || io.alu_op === ALU_SLTU,
  46. cmp,
  47. Mux(
  48. io.alu_op === ALU_SRA || io.alu_op === ALU_SRL,
  49. shiftr,
  50. Mux(
  51. io.alu_op === ALU_SLL,
  52. shiftl,
  53. Mux(
  54. io.alu_op === ALU_AND,
  55. io.A & io.B,
  56. Mux(
  57. io.alu_op === ALU_OR,
  58. io.A | io.B,
  59. Mux(io.alu_op === ALU_XOR, io.A ^ io.B,
  60. // COMB指令执行
  61. Mux(io.alu_op === ALU_COMB, comb, Mux(io.alu_op===ALU_COPY_A, io.A, io.B)))
  62. )
  63. )
  64. )
  65. )
  66. )
  67. )
  68. io.out := out
  69. io.sum := sum
  70. }

对 comb 指令进行测试

步骤 4.1:编写测试程序

编写一个程序加载到处理器中,调用 comb 指令并观察结果。comb.s代码如下:

  1. .text # Define beginning of text section
  2. .global _start # Define entry _start
  3. _start:
  4. lui x6, 1 # x6 = 0x00001000
  5. lui x7, 2 # x7 = 0x00002000
  6. # comb x5, x6, x7
  7. exit:
  8. csrw mtohost, 1
  9. j exit
  10. .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 的设置为:

  1. opcode: 0110011
  2. funct3: 110
  3. funct7: 0000001

在 Instructions.scala 文件中添加以下内容:

  1. // Instructions.scala
  2. import chisel3.util.BitPat
  3. object Instructions {
  4. // 已有指令省略
  5. def NEW_INST = BitPat("b0000001??????????110?????0110011")
  6. }

2. 添加 comb 指令的译码

步骤 2.1:定义 ALU 操作常量

在 Alu.scala 文件中为 new_inst 指令添加常量:

  1. // Alu.scala
  2. object Alu {
  3. // 已有常量省略
  4. val ALU_NEW_INST = 13.U(4.W) // 新指令对应的 ALU 操作常量
  5. }

步骤 2.2:设置控制信号映射

修改 Control.scala 文件,设置指令控制信号。为 new_inst 指令添加控制信号映射,确保译码后能正确触发对应的 ALU 操作:

  1. // Control.scala
  2. val map = Array(
  3. // 已有映射省略
  4. 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)
  5. )

3. 实现 new_inst 指令的执行操作

步骤 3.1:实现指令逻辑

为 new_inst 添加操作(~io.A & io.B 表示对 rs1 的值取反后与 rs2 进行按位与操作):

  1. // Alu.scala
  2. class AluSimple(val width: Int) extends Alu {
  3. val io = IO(new AluIO(width))
  4. io.out := MuxLookup(
  5. io.alu_op,
  6. io.B,
  7. Seq(
  8. // 已有操作省略
  9. ALU_NEW_INST -> (~io.A & io.B) // 新指令逻辑实现
  10. )
  11. )
  12. }

要在 AluArea 中添加对 new_inst 指令的支持,进行以下步骤:

  1. 定义指令逻辑:new_inst 的功能是对 rs1 取反后与 rs2 按位与,将结果存储到 rd。对应到硬件逻辑,可以直接通过 ~io.A & io.B 实现。
  2. 在ALU的Mux逻辑中插入新指令处理逻辑:在out的生成逻辑中添加new_inst的逻辑。
  3. 确保ALU操作码正确映射:在控制逻辑中定义ALU_NEW_INST并在AluArea中使用。
  1. class AluArea(val width: Int) extends Alu {
  2. val io = IO(new AluIO(width))
  3. val sum = io.A + Mux(io.alu_op(0), -io.B, io.B)
  4. val cmp = Mux(
  5. io.A(width - 1) === io.B(width - 1),
  6. sum(width - 1),
  7. Mux(io.alu_op(1), io.B(width - 1), io.A(width - 1))
  8. )
  9. val shamt = io.B(4, 0).asUInt
  10. val shin = Mux(io.alu_op(3), io.A, Reverse(io.A))
  11. val shiftr = (Cat(io.alu_op(0) && shin(width - 1), shin).asSInt >> shamt)(width - 1, 0)
  12. val shiftl = Reverse(shiftr)
  13. // 新增指令逻辑: rs1取反后与rs2按位与
  14. val newInst = ~io.A & io.B
  15. // 其他指令逻辑
  16. val comb = Cat(io.A(31, 16), io.B(15, 0))
  17. val out =
  18. Mux(
  19. io.alu_op === ALU_ADD || io.alu_op === ALU_SUB,
  20. sum,
  21. Mux(
  22. io.alu_op === ALU_SLT || io.alu_op === ALU_SLTU,
  23. cmp,
  24. Mux(
  25. io.alu_op === ALU_SRA || io.alu_op === ALU_SRL,
  26. shiftr,
  27. Mux(
  28. io.alu_op === ALU_SLL,
  29. shiftl,
  30. Mux(
  31. io.alu_op === ALU_AND,
  32. io.A & io.B,
  33. Mux(
  34. io.alu_op === ALU_OR,
  35. io.A | io.B,
  36. Mux(
  37. io.alu_op === ALU_XOR,
  38. io.A ^ io.B,
  39. Mux(
  40. io.alu_op === ALU_COMB,
  41. comb,
  42. Mux(
  43. io.alu_op === ALU_NEW_INST, // 新增指令的逻辑
  44. newInst,
  45. Mux(io.alu_op === ALU_COPY_A, io.A, io.B)
  46. )
  47. )
  48. )
  49. )
  50. )
  51. )
  52. )
  53. )
  54. )
  55. io.out := out
  56. io.sum := sum
  57. }

4. 对 new_inst 指令进行测试

步骤 4.1:编写测试程序

编写一个程序加载到处理器中,调用 new_inst 指令并观察结果。new_inst.s代码如下:

  1. .text # Define beginning of text section
  2. .global _start # Define entry _start
  3. _start:
  4. lui x6, 1 # x6 = 0x00001000
  5. lui x7, 2 # x7 = 0x00002000
  6. # new_inst x5, x6, x7
  7. exit:
  8. csrw mtohost, 1
  9. j exit
  10. .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 |

手算验证:

  1. ~x6 = 0xFFFFEFFF 的二进制表示为:1111 1111 1111 1110 1111 1111 1111 1111。
  2. x7 = 0x00002000 的二进制表示为:0000 0000 0000 0010 0000 0000 0000 0000。
  3. 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 寄存器。

  1. x6 = 0x00001000 的二进制表示为:00000000 00000001 00000000 00000000。
  2. 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 中。

  1. x6 = 0x00001000 的二进制表示为:00000000 00000001 00000000 00000000。
  2. 对 x6 进行按位取反(~x6),得到 0xFFFFEFFF,即二进制表示为:11111111 11111111 11101111 11111111。
  3. x7 = 0x00002000 的二进制表示为:00000000 00000010 00000000 00000000。
  4. 按位与操作 ~x6 与 x7:
  1. 11111111 11111111 11101111 11111111 (x6取反)
  2. AND
  3. 00000000 00000010 00000000 00000000 (x7)

  4. 00000000 00000010 00000000 00000000 (结果)

得到的结果是 0x00002000,所以 x5 的值为 0x00002000,指令执行正确。

相关推荐
费曼的黑板4 分钟前
国产低功耗带LCD驱动和触摸按键功能的MCU
单片机·嵌入式硬件
小猪写代码1 小时前
STM32 FreeRTOS内存管理简介
stm32·单片机
电工小王(全国可飞)4 小时前
STM32F407 内部参考电压校准实现 HAL库
stm32·单片机·嵌入式硬件
嵌入式小强工作室5 小时前
STM32更新程序OTA
stm32·单片机·嵌入式硬件
gyeolhada7 小时前
计算机组成原理(计算机系统3)--实验八:处理器结构拓展实验
java·前端·数据库·嵌入式硬件
andylauren14 小时前
(5)STM32 USB设备开发-USB键盘
stm32·嵌入式硬件·计算机外设
Ronin-Lotus15 小时前
嵌入式硬件篇---ADC模拟-数字转换
笔记·stm32·单片机·嵌入式硬件·学习·低代码·模块测试
promising-w15 小时前
单片机基础模块学习——数码管
单片机·嵌入式硬件·学习
华清远见IT开放实验室16 小时前
嵌入式STM32创新教学:华清远见虚拟仿真实验平台与智能车项目师资培训
stm32·单片机·嵌入式硬件