This test is divided into three parts:
Table of Contents
Design decoding circuit
Design register file
Implement a 32-word instruction memory
Design decoding circuit
Input a 32-bit machine word, and complete the decoding of add, sub, lw, and sw instructions according to the textbook MIPS instruction format, and all other instructions will be decoded into nop instructions. The input signal is named Instr_word. The decoded output signals for the above four instructions are named add_op, sub_op, lw_op and sw_op. The other instructions are all decoded as nop;
Given the Chisel design code and simulation test waveform, observe that the input Instr_word is add R1, R2, R3; sub R0, R5, R6, lw R5,100(R2), sw R5,104(R2), JAL RA,100(R2) ), the corresponding output waveform
Decode.scala
import chisel3._ class Decoder extends Module { val io = IO(new Bundle { val Instr_word = Input(UInt(32.W)) val add_op = Output(Bool()) val sub_op = Output(Bool()) val lw_op = Output(Bool()) val sw_op = Output(Bool()) val nop_op = Output(Bool()) }) //Define opcode val OPCODE_ADD = "b000000".U val OPCODE_SUB = "b000000".U val OPCODE_LW = "b100011".U val OPCODE_SW = "b101011".U //Define function code val FUNCT_ADD = "b100000".U val FUNCT_SUB = "b100010".U //Extract the opcode of the MIPS instruction val opcode = io.Instr_word(31, 26) //Extract the function code of the MIPS instruction val funct = io.Instr_word(5, 0) // decode io.add_op := opcode === OPCODE_ADD & amp; & amp; funct === FUNCT_ADD io.sub_op := opcode === OPCODE_SUB & amp; & amp; funct === FUNCT_SUB io.lw_op := opcode === OPCODE_LW io.sw_op := opcode === OPCODE_SW io.nop_op := !(io.add_op || io.sub_op || io.lw_op || io.sw_op) } object Decoder extends App { (new chisel3.stage.ChiselStage).emitVerilog(new Decoder()) }
DecoderTest.scala
import chiseltest._ import org.scalatest.flatspec.AnyFlatSpec import chisel3._ class DecoderTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "Decoder" it should "correctly decode instructions" in { test(new Decoder).withAnnotations(Seq(WriteVcdAnnotation)) { c => //Test instructions val addInstruction = "b000000_00010_00011_00001_00000_100000".U val subInstruction = "b000000_00101_00110_00000_00000_100010".U val lwInstruction = "b100011_00010_00101_0000000001100100".U val swInstruction = "b101011_00010_00101_0000000001101000".U val jalInstruction = "b000011_00000_00000000000000000000".U // Set the input instruction and evaluate the decoder c.io.Instr_word.poke(addInstruction) c.clock.step() c.io.add_op.expect(true) c.io.sub_op.expect(false) c.io.lw_op.expect(false) c.io.sw_op.expect(false) c.io.nop_op.expect(false) c.io.Instr_word.poke(subInstruction) c.clock.step() c.io.add_op.expect(false) c.io.sub_op.expect(true) c.io.lw_op.expect(false) c.io.sw_op.expect(false) c.io.nop_op.expect(false) c.io.Instr_word.poke(lwInstruction) c.clock.step() c.io.add_op.expect(false) c.io.sub_op.expect(false) c.io.lw_op.expect(true) c.io.sw_op.expect(false) c.io.nop_op.expect(false) c.io.Instr_word.poke(swInstruction) c.clock.step() c.io.add_op.expect(false) c.io.sub_op.expect(false) c.io.lw_op.expect(false) c.io.sw_op.expect(true) c.io.nop_op.expect(false) c.io.Instr_word.poke(jalInstruction) c.clock.step() c.io.add_op.expect(false) c.io.sub_op.expect(false) c.io.lw_op.expect(false) c.io.sw_op.expect(false) c.io.nop_op.expect(true) } } }
Design register file
There are a total of 32 32-bit registers, allowing two reads and one write, and register 0 has a fixed readout bit of 0. The four input signals are RS1, RS2, WB_data, and Reg_WB, and the register outputs RS1_out and RS2_out; the initial value stored inside the register is equal to the register number.
Given the Chisel design code and simulation test waveform, observe the output waveform of RS1=5, RS2=8, WB_data=0x1234, Reg_WB=1 and the values of the affected registers.
Register.scala
import chisel3._ import chisel3.util._ class RegisterFile extends Module { val io = IO(new Bundle { val RS1 = Input(UInt(5.W)) // RS1 input signal, used to select the register to be read val RS2 = Input(UInt(5.W)) // RS2 input signal, used to select the register to be read val WB_data = Input(UInt(32.W)) //Write data signal, used to write registers val Reg_WB = Input(UInt(5.W)) //Select the register to write data to val RS1_out = Output(UInt(32.W)) // RS1 output data val RS2_out = Output(UInt(32.W)) // RS2 output data }) val registers = RegInit(VecInit((0 until 32).map(_.U(32.W)))) // 32 32-bit registers, the initial value is equal to the register number registers(io.Reg_WB) := io.WB_data // Write data to register io.RS1_out := Mux(io.RS1 === 0.U, 0.U, registers(io.RS1)) // RS1 outputs data, register 0 is fixed to read bit 0 io.RS2_out := Mux(io.RS2 === 0.U, 0.U, registers(io.RS2)) // RS2 output data, register 0 is fixed to read bit 0 } object RegisterFile extends App { (new chisel3.stage.ChiselStage).emitVerilog(new RegisterFile()) }
RegisterTest.scala
import chisel3._ import chiseltest._ import org.scalatest.flatspec.AnyFlatSpec import chisel3.util._ class RegisterFileTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "RegisterFile" it should "correctly update and read registers" in { test(new RegisterFile).withAnnotations(Seq(WriteVcdAnnotation)) { c => //Set input signal c.io.RS1.poke(5.U) c.io.RS2.poke(8.U) c.io.WB_data.poke(0x1234.U) c.io.Reg_WB.poke(1.U) c.clock.step() c.io.RS1_out.expect(5.U) c.io.RS2_out.expect(8.U) } } }
Implementing a 32-word instruction memory
Store 4 instructions add R1, R2, R3 from address 0; sub R0, R5, R6, lw R5,100(R2), sw R5,104(R2). Then combine the instruction memory, register file, decoding circuit, and combine it with the PC update circuit (the initial value of PC is 0), WB_data and Reg_WB signal generation circuit, and finally allow the circuit to fetch and decode instructions one by one (without completing the instruction execution).
The Chisel design code and simulation test waveforms are given, the execution process waveforms of the four instructions are observed, and their meanings are recorded and explained.
InstructionMemory.scala
import chisel3._ class InstructionMemory extends Module { val io = IO(new Bundle { val address = Input(UInt(5.W)) // 32 words, 5-bit address required val instruction = Output(UInt(32.W)) }) //Create a 32-word instruction memory val mem = Mem(32, UInt(32.W)) //Initialize memory and store MIPS instructions mem.write(0.U, "b000000_00010_00011_00001_00000_100000".U) // add R1, R2, R3 mem.write(1.U, "b000000_00101_00110_00000_00000_100010".U) // sub R0, R5, R6 mem.write(2.U, "b100011_00010_00101_0000000001100100".U) // lw R5, 100(R2) mem.write(3.U, "b101011_00010_00101_0000000001101000".U) // sw R5, 104(R2) //Read instructions from memory io.instruction := mem.read(io.address) }
Circuit.scala
import chisel3._ import chisel3.util._ class Circuit extends Module { val io = IO(new Bundle { //Register input and output val WB_data = Input(UInt(32.W)) //Write data signal, used to write registers val Reg_WB = Input(UInt(5.W)) //Select the register to write data to val RS1_out = Output(UInt(32.W)) val RS2_out = Output(UInt(32.W)) // decode val add_op = Output(Bool()) val sub_op = Output(Bool()) val lw_op = Output(Bool()) val sw_op = Output(Bool()) val nop_op = Output(Bool()) }) val instructionMemory = Module(new InstructionMemory) val registerFile = Module(new RegisterFile) val decoder = Module(new Decoder) val pc = RegInit(0.U(5.W)) // Get the corresponding instruction from the instruction register based on the value of pc instructionMemory.io.address := pc decoder.io.Instr_word := instructionMemory.io.instruction registerFile.io.RS1 := instructionMemory.io.instruction(25, 21) registerFile.io.RS2 := instructionMemory.io.instruction(20, 16) registerFile.io.WB_data := (0.U(32.W)) registerFile.io.Reg_WB := (0.U(5.W)) // update output io.RS1_out := registerFile.io.RS1_out io.RS2_out := registerFile.io.RS2_out io.add_op := decoder.io.add_op io.sub_op := decoder.io.sub_op io.lw_op := decoder.io.lw_op io.sw_op := decoder.io.sw_op io.nop_op := decoder.io.nop_op //Update PC pc := pc + 1.U } object Circuit extends App { (new chisel3.stage.ChiselStage).emitVerilog(new Circuit()) }
CircuitTest.scala
import chiseltest._ import org.scalatest.flatspec.AnyFlatSpec import chisel3._ class CircuitTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "Circuit" it should "correct circuit" in { test(new Circuit).withAnnotations(Seq(WriteVcdAnnotation)) { c => c.clock.step() c.clock.step() c.clock.step() c.clock.step() } } }