owned this note
owned this note
Published
Linked with GitHub
# 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.
```scala
// 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 :
:::danger
:warning: **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

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.
```scala
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.
```scala
object InstructionsTypeS {
val sb = "b000".U
val sh = "b001".U
val sw = "b010".U
}
```
#### Waveform analysis

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

- ```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```.

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```:
```scala
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
```c
addi sp,sp,-16
```

- 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
```c
la t0, cmp_data_1
```

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
```c
sw t0, 0(sp)
```

- 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```.