# Assignment3: single-cycle RISC-V CPU
contributed by < [`KuanYuan0530`](https://github.com/KuanYuan0530) >
## Environment
Reference: [Lab3: Construct a single-cycle RISC-V CPU with Chisel](https://hackmd.io/@sysprog/r1mlr3I7p#Chisel-Tutorial)
### Install the dependent packages
```shell
$ sudo apt install build-essential verilator gtkwave
```
### Install sbt, Scala Build Tool
```shell
# Install sdkman
$ curl -s "https://get.sdkman.io" | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
# Install Eclipse Temurin JDK 11
$ sudo sdk install java 11.0.21-tem
$ sdk install sbt
```
### [Chisel](https://www.chisel-lang.org/) Tutorial
Getting the Repository:
```shell
$ git clone https://github.com/ucb-bar/chisel-tutorial
$ cd chisel-tutorial
$ git checkout release
```
Before testing your system, ensure that you have sbt (the Scala build tool) installed.
```shell
$ sbt run
```
Reference output:
```
[info] Loading project definition from /home/chen/chisel-tutorial/project
[info] Loading settings for project chisel-tutorial from build.sbt ...
[info] Set current project to chisel-tutorial (in build file:/home/chen/chisel-tutorial/)
[info] running hello.Hello
[info] [0.001] Elaborating design...
[info] [0.140] Done elaborating.
Computed transform order in: 385.6 ms
Total FIRRTL Compile Time: 720.5 ms
End of dependency graph
Circuit state created
[info] [0.002] SEED 1701259036483
test Hello Success: 1 tests passed in 6 cycles taking 0.026425 seconds
[info] [0.007] RAN 1 CYCLES PASSED
```
## Using Docker to practice Chisel
### Install [Docker](https://docs.docker.com/engine/install/ubuntu/#installation-methods)
1. Set up Docker's apt repository.
```shell
# Add Docker's official GPG key:
$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
$ echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update
```
2. Install the Docker packages.
```shell
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```
3. Verify that the Docker Engine installation is successful by running the hello-world image.
```shell
$ sudo docker run hello-world
```
Result:
```
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
719385e32844: Pull complete
Digest: sha256:c79d06dfdfd3d3eb04cafd0dc2bacab0992ebc243e083cabe208bac4dd7759e0
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
```
### Access Jupyter Notebook-based interactive computing platform
1. Run the following command
```shell
$ sudo docker run -it --rm -p 8888:8888 sysprog21/chisel-bootcamp
```
2. Paste URL into web browser
```
To access the notebook, open this file in a browser:
file:///home/bootcamp/.local/share/jupyter/runtime/nbserver-7-open.html
Or copy and paste one of these URLs:
http://27ff70194fdb:8888/?token=54ecb0708eff8540d0bff19602df817e8742f54166c22307
or http://127.0.0.1:8888/?token=54ecb0708eff8540d0bff19602df817e8742f54166c22307
```
### Learning Chisel by doing!
#### Hello World in Chisel
```scala
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
}
```
**CNT_MAX** is a constant value that represents the maximum count of the counter.**cntReg** is a temporary register for counting, initialized to 32-bit 0, and incremented by 1 at each clock cycle. **blkReg** is a flag indicating whether the maximum count is reached, initialized to 1 bit of 0. When the count reaches the maximum value, cntReg will be reset to 0, blkReg will be inverted, and blkReg will be output give to **io.led**.
#### Enhance Hello World by incorporating logic circuit
```scala
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 := Mux(cntReg === CNT_MAX, 0.U, cntReg + 1.U)
blkReg := Mux(cntReg === CNT_MAX, ~blkReg, blkReg)
io.led := blkReg
}
```
Enhance Hello World by incorporating logic circuit **Mux()** instead of **when()**.
## Implement Single-cycle RISC-V CPU
Get the repository:
```shell
$ git clone https://github.com/sysprog21/ca2023-lab3
$ cd ca2023-lab3
```
To simulate and run tests for this project, execute the following commands under the ca2023-lab3 directory.
```shell
$ sbt test
```
Result:
```
[info] *** 6 TESTS FAILED ***
[error] Failed tests:
[error] riscv.singlecycle.InstructionDecoderTest
[error] riscv.singlecycle.ByteAccessTest
[error] riscv.singlecycle.InstructionFetchTest
[error] riscv.singlecycle.ExecuteTest
[error] riscv.singlecycle.FibonacciTest
[error] riscv.singlecycle.QuicksortTest
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 67 s (01:07), completed Nov 29, 2023, 9:02:34 PM
```
Based on the above error message, we need to complete the following code.
:::info
src/main/scala/riscv/core/InstructionFetch.scala
src/main/scala/riscv/core/InstructionDecode.scala
src/main/scala/riscv/core/Execute.scala
src/main/scala/riscv/core/CPU.scala
:::
We can run a single test, such as running only InstructionDecoderTest, execute the following command:
```shell
$ sbt "testOnly riscv.singlecycle.InstructionDecoderTest"
```
Execute following command to generate waveform:
```shell
$ WRITE_VCD=1 sbt test
```
Afterward, open .vcd files in various subdirectories under the test_run_dir directory with GTKWave.
### Find the filled code from [my repository](https://github.com/KuanYuan0530/ca2023-lab3)
### Instruction Fetch
```shell
$ sbt "testOnly riscv.singlecycle.InstructionFetchTest"
```
Test result:
```
[info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 11.0.21)
[info] loading settings for project ca2023-lab3-build from plugins.sbt ...
[info] loading project definition from /home/chen/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/home/chen/ca2023-lab3/)
[info] InstructionFetchTest:
[info] InstructionFetch of Single Cycle CPU
[info] - should fetch instruction
[info] Run completed in 10 seconds, 718 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: 13 s, completed Dec 1, 2023, 8:01:49 AM
```
#### Waveform Analysis
Result:

Based on the above waveform, when **io.jump_flag_id** is 0, it indicates that no jump has occurred. In this case, the instruction **pc := pc + 4** will be executed, causing the program counter (pc) to point to the next instruction. When **io.jump_flag_id** is 1, it signifies that a jump has occurred. Consequently, the **pc will be set equal to io.jump_address_id**, directing it to the target instruction of the jump (where io.instruction_address corresponds to the signal of the program counter).
### Instruction Decode
```shell
$ sbt "testOnly riscv.singlecycle.InstructionDecoderTest"
```
Test result:
```
[info] InstructionDecoderTest:
[info] InstructionDecoder of Single Cycle CPU
[info] - should produce correct control signal
[info] Run completed in 13 seconds, 214 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: 31 s, completed Dec 1, 2023, 12:26:27 PM
```
#### Waveform Analysis
opcode list:
```scala
object InstructionTypes {
val L = "b0000011".U
val I = "b0010011".U
val S = "b0100011".U
val RM = "b0110011".U
val B = "b1100011".U
}
```
Result:

Using a Mux based on the value of the opcode, it is determined whether a read or write operation is required. When the instruction belongs to the Load type, io.memory_read_enable is set to 1; otherwise, it is set to 0. When the instruction belongs to the Store type, io.memory_write_enable is set to 1; otherwise, it is set to 0.
Therefore, based on the waveform and opcode list provided, when **opcode is b0000011**, **io.memory_read_enable is set to 1**. When **opcode is b0100011**, **io.memory_write_enable is set to 1**. In all other cases, both io.memory_read_enable and io.memory_write_enable are set to 0.
### Execution
```shell
$ sbt "testOnly riscv.singlecycle.ExecuteTest"
```
Test result:
```
[info] ExecuteTest:
[info] Execution of Single Cycle CPU
[info] - should execute correctly
[info] Run completed in 9 seconds, 828 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: 21 s, completed Dec 1, 2023, 2:31:21 PM
```
#### Waveform Analysis
```scala
object ALUOp1Source {
val Register = 0.U(1.W)
val InstructionAddress = 1.U(1.W)
}
object ALUOp2Source {
val Register = 0.U(1.W)
val Immediate = 1.U(1.W)
}
```
Result:


Based on the alu_ctrl, the operation to be performed by the ALU is determined, and the value is assigned to alu.io.func. Subsequently, based on the InstructionDecode, it is decided whether the first operand for the ALU is from the Register or the Instruction Address, and whether the second operand is from the Register or the Immediate.
Therefore, according to the given waveform, when **io.aluop1_source.asBool is 1**, it indicates that **alu.io.op1** equal **InstructionAddress**. When **io.aluop1_source.asBool is 0**, then **alu.io.op1** equal **io.reg1_data**. Similarly, when **io.aluop2_source.asBool is 1**, it signifies that **alu.io.op2** equal **io.immediate**. When **io.aluop2_source.asBool is 0**, then **alu.io.op2** equal **io.reg2_data**.
### Combining into a CPU
```shell
$ sbt "testOnly riscv.singlecycle.FibonacciTest"
```
```
[info] set current project to mycpu (in build file:/home/chen/ca2023-lab3/)
[info] compiling 1 Scala source to /home/chen/ca2023-lab3/target/scala-2.13/classes ...
[info] FibonacciTest:
[info] Single Cycle CPU
[info] - should recursively calculate Fibonacci(10)
[info] Run completed in 16 seconds, 161 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: 29 s, completed Dec 1, 2023, 3:56:56 PM
```
```shell
$ sbt "testOnly riscv.singlecycle.QuicksortTest"
```
```
[info] set current project to mycpu (in build file:/home/chen/ca2023-lab3/)
[info] QuicksortTest:
[info] Single Cycle CPU
[info] - should perform a quicksort on 10 numbers
[info] Run completed in 16 seconds, 895 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: 21 s, completed Dec 1, 2023, 3:59:46 PM
```
### After complete the code
```shell
$ sbt test
```
Result:
```shell
[info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 11.0.21)
[info] loading settings for project ca2023-lab3-build from plugins.sbt ...
[info] loading project definition from /home/chen/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/home/chen/ca2023-lab3/)
[info] InstructionFetchTest:
[info] InstructionFetch of Single Cycle CPU
[info] - should fetch instruction
[info] FibonacciTest:
[info] Single Cycle CPU
[info] - should recursively calculate Fibonacci(10)
[info] QuicksortTest:
[info] Single Cycle CPU
[info] - should perform a quicksort on 10 numbers
[info] ExecuteTest:
[info] Execution of Single Cycle CPU
[info] - should execute correctly
[info] RegisterFileTest:
[info] Register File of Single Cycle CPU
[info] - should read the written content
[info] - should x0 always be zero
[info] - should read the writing content
[info] InstructionDecoderTest:
[info] InstructionDecoder of Single Cycle CPU
[info] - should produce correct control signal
[info] ByteAccessTest:
[info] Single Cycle CPU
[info] - should store and load a single byte
[info] Run completed in 52 seconds, 788 milliseconds.
[info] Total number of tests run: 9
[info] Suites: completed 7, aborted 0
[info] Tests: succeeded 9, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 55 s, completed Dec 1, 2023, 4:09:15 PM
```