Try โ€‚โ€‰HackMD

Assignment3: Single-cycle RISC-V CPU

contributed by ???

Hello World in Chisel

Set a time unit CNT_MAX, and change the value of io.led (blkReg's value) for each time unit to achieve the blinking effect of the LED light.

// Hello World in Chisel
class Hello extends Module {
  val io = IO(new Bundle {
    val led = Output(UInt(1.W))
  })
  val CNT_MAX = (50000000 / 2 - 1).U;
  val cntReg  = RegInit(0.U(32.W))
  val blkReg  = RegInit(0.U(1.W))
  cntReg := cntReg + 1.U
  when(cntReg === CNT_MAX) {
    cntReg := 0.U
    blkReg := ~blkReg                                                         
  }
  io.led := blkReg
}

Single Cycle RISC-V CPU

Once the first three files are completed, connect them with other components to form a complete CPU.

  • InstructionFetch.scala
  • InstructionDecode.scala
  • Execute.scala
  • CPU.scala

InstructionFetch.scala :

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’
Refrain from copying and pasting your solution directly into the HackMD note. Instead, provide a concise summary of the various test cases, outlining the aspects of the CPU they evaluate, the techniques employed for loading test program instructions, and the outcomes of these test cases.

During the IF (Instruction Fetch) stage, determine the PC source by checking io.jump_flag_id. If a JUMP is not taken, the instructions enter the CPU in their original order, and the next instruction is PC + 4.

Waveform analysis

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

From the above diagram, it can be observed that with each clock cycle change, the personal computer (PC) will continue to increment by 4 until io_jump_flag_id is set to 1 (on the rising edge of the wave). This signifies that the instruction is a jump, and the next instruction will not be PC+4.

InstructionDecode.scala

When the opcode of the instruction is type L, which is a LOAD instruction, it is necessary to read the instruction from memory. In this case, set io.memory_read_enable to 1.

object InstructionsTypeL {
  val lb  = "b000".U
  val lh  = "b001".U
  val lw  = "b010".U
  val lbu = "b100".U
  val lhu = "b101".U
}

When the opcode of the instruction is type S, which is a STORE instruction, it is necessary to write the instruction to memory. In this case, set io.memory_write_enable to 1.

object InstructionsTypeS {
    val sb = "b000".U
    val sh = "b001".U
    val sw = "b010".U
}

Waveform analysis

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

From the above diagram, it can be seen that when the opcode is 0x23, indicating an S-type instruction, io_memory_write_enable should be set to 1 (on the rising edge of the wave). This signifies that data can be written into memory.

Execute.scala

Waveform analysis

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

  • io.aluop1_source determines the first source of the ALU. When io.aluop1_source is set to 1 (on the rising edge of the wave), alu.io.op1 is connected to io.instruction_address. When io.aluop1_source is set to 0 (on the falling edge of the wave), alu.io.op1 is connected to io.reg1_data.
  • io.aluop2_source determines the second source of the ALU. When io.aluop2_source is set to 1 (on the rising edge of the wave), alu.io.op2 is connected to io.immediate. When io.aluop2_source is set to 0 (on the falling edge of the wave), alu.io.op2 is connected to io.reg2_data.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

ALU, as an arithmetic logic unit, performs mathematical operations based on the specified function. The demonstration above illustrates addition. ex:io_reg1_data+io_reg2_data = alu_io_result

CPU.scala :

In the preceding stages, the connections within each component have been roughly established. In CPU.scala, simply connecting the inputs and outputs between different components completes a ready-made CPU.

Test the assembly code for HW2

Transplant the test.S file from HW2 to the target directory. Modify the makefile to convert the .S file to a .asmbin file for execution on Chisel. Simultaneously, add a class named HW2Test in CPUTest.scala to test the output of test.asmbin.

The following is the content of HW2Test:

class HW2Test extends AnyFlatSpec with ChiselScalatestTester {
    behavior.of("Single Cycle CPU")
    it should "perform HW2" in {
        test(new TestTopModule("test.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
    	    for (i <- 1 to 50) {
			c.clock.step(1000)
			c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
	    }
            c.io.mem_debug_read_address.poke(0.U)
            c.clock.step()
            c.io.mem_debug_read_data.expect(0.U)

            c.io.mem_debug_read_address.poke(4.U)
            c.clock.step()
            c.io.mem_debug_read_data.expect(0.U)

            c.io.mem_debug_read_address.poke(8.U)			
            c.clock.step()
            c.io.mem_debug_read_data.expect(1.U)

            c.io.mem_debug_read_address.poke(12.U)
            c.clock.step()
            c.io.mem_debug_read_data.expect(1.U)
	    }
	}		
}

The following figure depicts the experimental results.

[info] lab2Test:
[info] Single Cycle CPU
[info] - should perform lab2
[info] Run completed in 7 seconds, 114 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 12 s, completed Nov 27, 2023, 10:26:36 PM

The original code printed the results through an ecall. In this experiment, a notable difference is that you store the experimental results in the target memory. During testing, you input the corresponding answers at the respective memory locations. If it is "success", it indicates that the experimental results match your expectations.

Waveform analysis

First instruction

addi sp,sp,-16

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

  • Instruction ff010113 is assembly code for addi sp,sp,-16.
  • addi is an I-type instruction, so the immediate value is -16 (0xFFFFFFF0).
  • Because the value of sp is 0, adding -16 results in -16. Therefore, io_result and io_regs_write_data have values of FFFFFFF0.

Second instruction

la t0, cmp_data_1

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Chisel does not have the la instruction. As shown in the diagram, Chisel simulates the functionality of la using two instructions: 00000297 and 26028293.

Third instruction

sw t0, 0(sp)

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

  • Instruction 00512023 is assembly code for sw t0, 0(sp).
  • sw is an S-type instruction, so the immediate value is 0.
  • The target location in memory is sp + 0, which is -16 (FFFFFFF0).
  • The value of t0 is 00001264, coming from io_read_data2.