Try   HackMD

Assignment3: single-cycle RISC-V CPU

contributed by < KuanYuan0530 >

Environment

Reference: Lab3: Construct a single-cycle RISC-V CPU with Chisel

Install the dependent packages

$ sudo apt install build-essential verilator gtkwave

Install sbt, Scala Build Tool

# 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 Tutorial

Getting the Repository:

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

$ 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

  1. Set up Docker's apt repository.
# 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
  1. Install the Docker packages.
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  1. Verify that the Docker Engine installation is successful by running the hello-world image.
$ 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
$ sudo docker run -it --rm -p 8888:8888 sysprog21/chisel-bootcamp
  1. 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

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

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:

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

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

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:

$ sbt "testOnly riscv.singlecycle.InstructionDecoderTest"

Execute following command to generate waveform:

$ 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

Instruction Fetch

$ 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:
image
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

$ 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:

object InstructionTypes {
  val L  = "b0000011".U
  val I  = "b0010011".U
  val S  = "b0100011".U
  val RM = "b0110011".U
  val B  = "b1100011".U
}

Result:
image
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

$ 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

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:
image
image
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

$ 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
$ 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

$ sbt 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] 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