Bootstrap

计算机组成原理(计算机系统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(40).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(40).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 - 10)
  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.               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(40).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 - 10)
  12.   val shiftl = Reverse(shiftr)
  13.   // 新增指令逻辑: rs1取反后与rs2按位与
  14.   val newInst = ~io.A & io.B
  15.   // 其他指令逻辑
  16.   val comb = Cat(io.A(3116), io.B(150))
  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.               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. --------------------------------------
  5. 00000000 00000010 00000000 00000000  (结果)

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

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;