# Assignment3: Single-cycle RISC-V CPU contributed by < [`SUE3K`](https://github.com/SUE3K/ca2023-lab3) > ## Hello World in Chisel ### Orignal code ```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 } ``` --- ```scala val CNT_MAX = (50000000 / 2 - 1).U; //the upper limit of the counter in order to control the speed of LED switching. ``` ```scala 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. ```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) 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](https://hackmd.io/_uploads/HJBv3ybSa.png) 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](https://hackmd.io/_uploads/rJQBpy-ra.png) opcode: 00A02223 -> binary : 0000 0000 1010 0000 0010 0010 0**010 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](https://hackmd.io/_uploads/Hye6pybrp.png) 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 ![Single-cycle CPU architecture](https://hackmd.io/_uploads/SJzK891ra.png) #### 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 ```scala 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: ```scala 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](https://hackmd.io/_uploads/ryuSfVmrp.png) Successful waveform: ![image](https://hackmd.io/_uploads/rkbW5TrB6.png) 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.