Try   HackMD

Assignment3: Single-cycle RISC-V CPU

contributed by < SUE3K >

Hello World in Chisel

Orignal code

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
}

val CNT_MAX = (50000000 / 2 - 1).U;
//the upper limit of the counter in order to control the speed of LED switching.
cntReg := cntReg + 1.U
//The counter is incremented by 1 every cycle.

When the counting register reaches CNT_MAX (cntReg === CNT_MAX) , execute

  1. Reset counter to 0 (cntReg := 0.U)
  2. Inverted the blkReg (blkReg := ~blkReg)
  3. Assign the value of blkReg to the LED output io.led realizing the LED state switching. (io.led := blkReg)

After enhancement

enhance it by incorporating logic circuit.
We use Mux to incorporate some logic circuit, and make the code more concise and clear.

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)
 io.led := Mux(cntReg === CNT_MAX, ~blkReg, blkReg)
}

environment setup

  • First we Install the dependent packages

For macOS

$ brew install verilator gtkwave

Install sbt

# Uninstall everything
$ brew uninstall sbt
$ brew uninstall jenv

# Install sdkman
$ curl -s "https://get.sdkman.io" | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"

# Install Eclipse Temurin JDK 11
$ sdk install java 11.0.21-tem 
$ sdk install sbt

But the latest version of "gtkwave" isn't compatable with macOS 14.
So I instead use Ubuntu to execute gtkwave.


Get the repository:

$ git clone https://github.com/SUE3K/ca2023-lab3
$ cd ca2023-lab3

Implementation : Single Cycle RISC-V CPU

We need to fill all blank to execute program.

  1. InstructionFetch.scala
  2. InstructionDecode.scala
  3. Execute.scala
  4. CPU.scala

InstructionFetch.scala

Test InstructionFetch

$ sbt "testOnly riscv.singlecycle.InstructionFetchTest"
[info] InstructionFetchTest:
[info] InstructionFetch of Single Cycle CPU
[info] - should fetch instruction
[info] Run completed in 1 second, 588 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: 46 s, completed 2023年11月27日 上午12:33:36

Analysis with GTKWave

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 →

When there is no jump signal, we execute pc+4 at 3ns.

InstructionDecode.scala

Test InstructionDecode

$ sbt "testOnly riscv.singlecycle.InstructionDecodeTest"
idea3c@Ians-MacBook ca2023-lab3 % sbt "testOnly riscv.singlecycle.InstructionDecodeTest"
[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 /Users/ian/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/Users/ian/ca2023-lab3/)
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] No tests to run for Test / testOnly
[success] Total time: 1 s, completed 2023年11月27日 上午12:34:19

Analysis with GTKWave

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 →

opcode: 00A02223 -> binary : 0000 0000 1010 0000 0010 0010 0010 0011

val opcode = io.instruction(6, 0)
val funct3 = io.instruction(14, 12)
val funct7 = io.instruction(31, 25)
val rd     = io.instruction(11, 7)
val rs1    = io.instruction(19, 15)
val rs2    = io.instruction(24, 20)

Here the instruction : 00A02223 is S-type.
Therefore activate the write_enable signal.

Execute.scala

Test Execute

$ sbt "testOnly riscv.singlecycle.ExecuteTest"
[info] ExecuteTest:
[info] Execution of Single Cycle CPU
[info] - should execute correctly
[info] Run completed in 1 second, 714 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: 3 s, completed 2023年11月27日 上午12:34:55

Analysis with GTKWave

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 →

  1. According to the signals (aluop1_source, aluop2_source) to execute ALU

  2. At the end of module, ALU will output the ruslt and signals(io_if_jump_flag, io_if_address)

CPU.scala

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 →

Test CPU

% sbt "testOnly riscv.singlecycle.CPUTest" 
[info] ExecuteTest:
[info] Execution of Single Cycle CPU
[info] - should execute correctly
[info] Run completed in 1 second, 714 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: 3 s, completed 2023年11月27日 上午12:34:55

Test

After we filled in all blanks, let’s test it out.

$ sbt test

Output(macOS):

idea3c@Ians-MacBook ca2023-lab3 % sbt test
[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 /Users/ian/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/Users/ian/ca2023-lab3/)
[info] InstructionFetchTest:
[info] InstructionFetch of Single Cycle CPU
[info] - should fetch instruction
[info] InstructionDecoderTest:
[info] InstructionDecoder of Single Cycle CPU
[info] - should produce correct control signal
[info] FibonacciTest:
[info] Single Cycle CPU
[info] - should recursively calculate Fibonacci(10) *** FAILED ***
[info]   java.lang.IllegalArgumentException: requirement failed: /Users/ian/ca2023-lab3/verilog/fibonacci.asmbin.txt is not a relative path
[info]   at scala.Predef$.require(Predef.scala:337)
[info]   at os.RelPath$.apply(Path.scala:292)
[info]   at treadle.executable.MemoryFileParser$.parse(Memory.scala:693)
[info]   at treadle.executable.MemoryInitializer.$anonfun$handleInitializers$3(Memory.scala:644)
[info]   at treadle.executable.MemoryInitializer.$anonfun$handleInitializers$3$adapted(Memory.scala:632)
[info]   at scala.collection.immutable.List.foreach(List.scala:333)
[info]   at treadle.executable.MemoryInitializer.handleInitializers(Memory.scala:632)
[info]   at treadle.executable.MemoryInitializer.<init>(Memory.scala:612)
[info]   at treadle.executable.ExecutionEngine.<init>(ExecutionEngine.scala:118)
[info]   at treadle.executable.ExecutionEngine$.apply(ExecutionEngine.scala:662)
[info]   ...
[info] ExecuteTest:
[info] Execution of Single Cycle CPU
[info] - should execute correctly
[info] QuicksortTest:
[info] ByteAccessTest:
[info] Single Cycle CPU
[info] Single Cycle CPU
[info] - should perform a quicksort on 10 numbers *** FAILED ***
[info] - should store and load a single byte *** FAILED ***
[info]   java.lang.IllegalArgumentException: requirement failed: /Users/ian/ca2023-lab3/verilog/quicksort.asmbin.txt is not a relative path
[info]   java.lang.IllegalArgumentException: requirement failed: /Users/ian/ca2023-lab3/verilog/sb.asmbin.txt is not a relative path
[info]   at scala.Predef$.require(Predef.scala:337)
[info]   at scala.Predef$.require(Predef.scala:337)
[info]   at os.RelPath$.apply(Path.scala:292)
[info]   at os.RelPath$.apply(Path.scala:292)
[info]   at treadle.executable.MemoryFileParser$.parse(Memory.scala:693)
[info]   at treadle.executable.MemoryFileParser$.parse(Memory.scala:693)
[info]   at treadle.executable.MemoryInitializer.$anonfun$handleInitializers$3(Memory.scala:644)
[info]   at treadle.executable.MemoryInitializer.$anonfun$handleInitializers$3(Memory.scala:644)
[info]   at treadle.executable.MemoryInitializer.$anonfun$handleInitializers$3$adapted(Memory.scala:632)
[info]   at treadle.executable.MemoryInitializer.$anonfun$handleInitializers$3$adapted(Memory.scala:632)
[info]   at scala.collection.immutable.List.foreach(List.scala:333)
[info]   at scala.collection.immutable.List.foreach(List.scala:333)
[info]   at treadle.executable.MemoryInitializer.handleInitializers(Memory.scala:632)
[info]   at treadle.executable.MemoryInitializer.handleInitializers(Memory.scala:632)
[info]   at treadle.executable.MemoryInitializer.<init>(Memory.scala:612)
[info]   at treadle.executable.ExecutionEngine.<init>(ExecutionEngine.scala:118)
[info]   at treadle.executable.MemoryInitializer.<init>(Memory.scala:612)
[info]   at treadle.executable.ExecutionEngine$.apply(ExecutionEngine.scala:662)
[info]   at treadle.executable.ExecutionEngine.<init>(ExecutionEngine.scala:118)
[info]   ...
[info]   at treadle.executable.ExecutionEngine$.apply(ExecutionEngine.scala:662)
[info]   ...
[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] Run completed in 2 seconds, 853 milliseconds.
[info] Total number of tests run: 9
[info] Suites: completed 7, aborted 0
[info] Tests: succeeded 6, failed 3, canceled 0, ignored 0, pending 0
[info] *** 3 TESTS FAILED ***
**[error] Failed tests:
[error] 	riscv.singlecycle.ByteAccessTest
[error] 	riscv.singlecycle.FibonacciTest
[error] 	riscv.singlecycle.QuicksortTest
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 4 s, completed 2023年11月27日 上午12:37:50**

Not sure why there are 3 TESTS FAILED on macOS.
But it can be executed successfully on Ubuntu.

Output(Ubuntu):

[info] welcome to sbt 1.9.7 (Temurin Java 1.8.0_392)
[info] loading settings for project ca2023-lab3-build from plugins.sbt ...
[info] loading project definition from /home/ianli/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/home/ianli/ca2023-lab3/)
[info] ExecuteTest:
[info] Execution of Single Cycle CPU
[info] - should execute correctly
[info] QuicksortTest:
[info] Single Cycle CPU
[info] - should perform a quicksort on 10 numbers
[info] ByteAccessTest:
[info] Single Cycle CPU
[info] - should store and load a single byte
[info] FibonacciTest:
[info] Single Cycle CPU
[info] - should recursively calculate Fibonacci(10)
[info] InstructionFetchTest:
[info] InstructionFetch of Single Cycle CPU
[info] - should fetch instruction
[info] InstructionDecoderTest:
[info] InstructionDecoder of Single Cycle CPU
[info] - should produce correct control signal
[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] Run completed in 25 seconds, 108 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: 26 s, completed Nov 27, 2023 12:38:39 AM

Adjust my RISC-V assembly to Run on MyCPU

  1. put my assembly into the /csrc directory
  2. Modify the Makefile (add myfile.asmbin into BINS)
  3. make update
  4. move myfile.asmbin into /src/main/resources
ianli@new:~/ca2023-lab3/csrc $ make update
riscv-none-elf-as -R -march=rv32i_zicsr -mabi=ilp32 -o init.o init.S
riscv-none-elf-gcc -O0 -Wall -march=rv32i_zicsr -mabi=ilp32 -c -o fibonacci.o fibonacci.c
riscv-none-elf-ld -o fibonacci.elf -T link.lds --oformat=elf32-littleriscv fibonacci.o init.o
riscv-none-elf-objcopy -O binary -j .text -j .data fibonacci.elf fibonacci.asmbin
riscv-none-elf-gcc -O0 -Wall -march=rv32i_zicsr -mabi=ilp32 -c -o hello.o hello.c
riscv-none-elf-ld -o hello.elf -T link.lds --oformat=elf32-littleriscv hello.o init.o
riscv-none-elf-objcopy -O binary -j .text -j .data hello.elf hello.asmbin
riscv-none-elf-as -R -march=rv32i_zicsr -mabi=ilp32 -o mmio.o mmio.S
riscv-none-elf-ld -o mmio.elf -T link.lds --oformat=elf32-littleriscv mmio.o
riscv-none-elf-objcopy -O binary -j .text -j .data mmio.elf mmio.asmbin
riscv-none-elf-gcc -O0 -Wall -march=rv32i_zicsr -mabi=ilp32 -c -o quicksort.o quicksort.c
riscv-none-elf-ld -o quicksort.elf -T link.lds --oformat=elf32-littleriscv quicksort.o init.o
riscv-none-elf-objcopy -O binary -j .text -j .data quicksort.elf quicksort.asmbin
riscv-none-elf-as -R -march=rv32i_zicsr -mabi=ilp32 -o sb.o sb.S
riscv-none-elf-ld -o sb.elf -T link.lds --oformat=elf32-littleriscv sb.o
riscv-none-elf-objcopy -O binary -j .text -j .data sb.elf sb.asmbin
riscv-none-elf-as -R -march=rv32i_zicsr -mabi=ilp32 -o hw3_liu.o hw3_liu.S
riscv-none-elf-ld -o hw3_liu.elf -T link.lds --oformat=elf32-littleriscv hw3_liu.o
riscv-none-elf-objcopy -O binary -j .text -j .data hw3_liu.elf hw3_liu.asmbin
cp -f fibonacci.asmbin hello.asmbin mmio.asmbin quicksort.asmbin sb.asmbin hw3_liu.asmbin ../src/main/resources
rm quicksort.elf fibonacci.elf hw3_liu.elf hello.elf sb.elf init.o mmio.elf


Add some instructions to make it more suitable for MYCPU

    .org 0
    .global _start          #starting address to linker
    
    .set SYSEXIT,  93       #add exit function
    .set SYSWRITE, 64       #add print function
    li   a7, SYSEXIT         # "exit" syscall
    ecall

Test my RISC-V assembly

Extend the CPUtest.scala function to test my RISC-V assembly code.
Store the data into memory and verify the results at address 0x4

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

Run test:

sbt "testOnly riscv.singlecycle.Hw3Test"

I couldn't pass the test at first.

ianli@new:~/ca2023-lab3 $ sbt "testOnly riscv.singlecycle.Hw3Test"
[info] welcome to sbt 1.9.7 (Temurin Java 1.8.0_392)
[info] loading settings for project ca2023-lab3-build from plugins.sbt ...
[info] loading project definition from /home/ianli/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/home/ianli/ca2023-lab3/)
[info] Hw3Test:
[info] Single Cycle CPU
[info] - should HW2-RISC-V *** FAILED ***
[info]   io_mem_debug_read_address=0 (0x0) did not equal expected=19 (0x1b) (lines in CPUTest.scala: 92, 84) (CPUTest.scala:92)
[info] Run completed in 5 seconds, 661 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
[error] Failed tests:
[error] 	riscv.singlecycle.Hw3Test
[error] (Test / testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 7 s, completed Nov 28, 2023 4:14:37 PM

So, I verified the test results by checking the waveform to ensure that the data was correctly written into the target register and memory address.

ianli@new:~/ca2023-lab3 $ sbt "testOnly riscv.singlecycle.Hw3Test"
[info] welcome to sbt 1.9.7 (Temurin Java 1.8.0_392)
[info] loading settings for project ca2023-lab3-build from plugins.sbt ...
[info] loading project definition from /home/ianli/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/home/ianli/ca2023-lab3/)
[info] compiling 1 Scala source to /home/ianli/ca2023-lab3/target/scala-2.13/test-classes ...
[info] Hw3Test:
[info] Single Cycle CPU
[info] - Should HW2-RISC-V
[info] Run completed in 6 seconds, 2 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: 14 s, completed Nov 30, 2023 4:13:58 PM

Use Verilator to debug

ianli@new:~/ca2023-lab3 $ make verilator
sbt "runMain board.verilator.VerilogGenerator"
[info] welcome to sbt 1.9.7 (Temurin Java 1.8.0_392)
[info] loading settings for project ca2023-lab3-build from plugins.sbt ...
[info] loading project definition from /home/ianli/ca2023-lab3/project
[info] loading settings for project root from build.sbt ...
[info] set current project to mycpu (in build file:/home/ianli/ca2023-lab3/)
[info] running board.verilator.VerilogGenerator 
[success] Total time: 5 s, completed Nov 28, 2023 5:14:44 PM
cd verilog/verilator && verilator --trace --exe --cc sim_main.cpp Top.v && make -C obj_dir -f VTop.mk
make[1]: Entering directory '/home/ianli/ca2023-lab3/verilog/verilator/obj_dir'
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow       -c -o sim_main.o ../sim_main.cpp
../sim_main.cpp: In member function ‘void Simulator::parse_args(const std::vector<std::__cxx11::basic_string<char> >&)’:
../sim_main.cpp:136:13: warning: init-statement in selection statements only available with ‘-std=c++17’ or ‘-std=gnu++17’
  136 |         if (auto it = std::find(args.begin(), args.end(), "-halt");
      |             ^~~~
../sim_main.cpp:141:13: warning: init-statement in selection statements only available with ‘-std=c++17’ or ‘-std=gnu++17’
  141 |         if (auto it = std::find(args.begin(), args.end(), "-memory");
      |             ^~~~
../sim_main.cpp:146:13: warning: init-statement in selection statements only available with ‘-std=c++17’ or ‘-std=gnu++17’
  146 |         if (auto it = std::find(args.begin(), args.end(), "-time");
      |             ^~~~
../sim_main.cpp:151:13: warning: init-statement in selection statements only available with ‘-std=c++17’ or ‘-std=gnu++17’
  151 |         if (auto it = std::find(args.begin(), args.end(), "-vcd");
      |             ^~~~
../sim_main.cpp:156:13: warning: init-statement in selection statements only available with ‘-std=c++17’ or ‘-std=gnu++17’
  156 |         if (auto it = std::find(args.begin(), args.end(), "-signature");
      |             ^~~~
../sim_main.cpp:164:13: warning: init-statement in selection statements only available with ‘-std=c++17’ or ‘-std=gnu++17’
  164 |         if (auto it = std::find(args.begin(), args.end(), "-instruction");
      |             ^~~~
/usr/bin/perl /usr/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include VTop.cpp > VTop__ALLcls.cpp
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow       -c -o VTop__ALLcls.o VTop__ALLcls.cpp
/usr/bin/perl /usr/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include VTop__Trace.cpp VTop__Syms.cpp VTop__Trace__Slow.cpp > VTop__ALLsup.cpp
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=1 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow       -c -o VTop__ALLsup.o VTop__ALLsup.cpp
ar -cr VTop__ALL.a VTop__ALLcls.o VTop__ALLsup.o
ranlib VTop__ALL.a
g++    sim_main.o verilated.o verilated_vcd_c.o VTop__ALL.a    -o VTop -lm -lstdc++ 
make[1]: Leaving directory '/home/ianli/ca2023-lab3/verilog/verilator/obj_dir'

Generate the dump.vcd

ianli@new:~/ca2023-lab3 $ ./run-verilator.sh -instruction src/main/resources/hw3_liu.asmbin -time 2000 -vcd dump.vcd
-time 2000
-memory 1048576
-instruction src/main/resources/hw3_liu.asmbin
[-------------------->] 100%

Tried checking the waveform to find the problem that caused the unsuccessful tests.

image

Successful waveform:

image

  1. io_alu_result calculate the answer = 19.
  2. Send the data to register a0.
  3. io_memory_write_enable=1 start to write the data into memory address 0x4 .
  4. Ultimately, pass through the test.