# Assignment3: single-cycle RISC-V CPU Contributed by [Yan-You Chen](https://github.com/y0y0alex/ca2023-lab3) ## Prepare ### envirnment: * use the Ubuntu 22.04 that homework2 already used ### Install the dependent packages ```shell $ sudo apt install build-essential verilator gtkwave ``` ### Install sbt ``` $ sudo apt-get install openjdk-11-jdk $ curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-linux.gz | gzip -d > cs && chmod +x cs && ./cs setup ``` ## Learning about Chisel * Chisel is an domain specific language (DSL) implemented using Scala’s macro features. * different write type between Chisel and usually C, `if(value == 12)` need to be rewrited `when(value === 12.U)` * read and learn the example from `/chisel-tutorial/src/main/scala/examples` , there are a lot of example. * read this web in the provided artical [Learn Chisel online!](https://hackmd.io/@sysprog/r1mlr3I7p#Chisel-Bootcamp) ## Chisel Tutorial ### Getting the Repository ``` $ git clone https://github.com/ucb-bar/chisel-tutorial $ cd chisel-tutorial $ git checkout release $ sbt run ``` * `sbt run` will try to run and you may see the follow message below. ``` [info] Loading project definition from /home/ca/chisel-tutorial/project [info] Loading settings for project chisel-tutorial from build.sbt ... [info] Set current project to chisel-tutorial (in build file:/home/ca/chisel-tutorial/) [info] running hello.Hello [info] [0.011] Elaborating design... [info] [0.208] Done elaborating. Computed transform order in: 405.3 ms Total FIRRTL Compile Time: 708.5 ms End of dependency graph Circuit state created [info] [0.001] SEED 1700628972323 test Hello Success: 1 tests passed in 6 cycles taking 0.028778 seconds [info] [0.008] RAN 1 CYCLES PASSED [success] Total time: 6 s, completed 2023年11月22日 下午12:56:17 ``` ## Single-cycle RISC-V CPU ### Implementation: Get the repository: ``` $ git clone https://github.com/sysprog21/ca2023-lab3 $ cd ca2023-lab3 ``` * This step get the architecture of Single-cycle RISC-V CPU that we need to write. ## Write MyCPU ### Add the code that we need to write in MyCPU * After I see the files I get, there are not many code we need to take. * In the dir `ca2023-lab/src/main/scala/riscv/core`, you only need to write * CPU.scala * InstructionFetch.scala * InstructionDecod.scala * Execute.scala * The blank that you should add is make by ``` lab3(***) begin lab3(***) end ``` * That means we only need to select some signal which correct and connect some wire out. * The detail that each stage is write in [professor provided](https://hackmd.io/@sysprog/r1mlr3I7p#Single-cycle-RISC-V-CPU). ### Some problem I take * First I try to use the `Muxlookup`, that instruction has already has a different format. Finally I change to use `Mux`. ### The code I write in each #### InstructionFetch * `io.jump_flag_id` should be used to decided the `io.jump_address_id` #### InstructionDecode * Initially, `io.memory_read_enable` and `io.memory_write_enable` don't determine. * The opcode should be decided by InstructionTypes #### Execute * We should write the `alu.io.op1`, `alu.io.op2 ` and `alu.io.func`. * We need check the `alu.io.op1` that `aluop1_source.asBool` , `io.instruction_address` or `io.reg1_data` which is source * `alu.io.op2` is like `alu.io.op1` but need some fixed * Then, will be connected to the output in the following code. #### CPU * The only way you need to do is connect the data from the ID stage to the EXE stage. ### each step test * after we write the scala code, we need to test ``` $ sbt "testOnly riscv.singlecycle.InstructionFetchTest" $ sbt "testOnly riscv.singlecycle.InstructionDecoderTest" $ sbt "testOnly riscv.singlecycle.ExecuteTest" ``` ### Functional Test * and then we can test some function. * already provide some code in `ca2023-lab3/csrc`, like 'Fibonacci sequence' * you can use the `sbt test` to test the code, if you write correct code you should pass all test. Like... > ....... > [info] <span style="color:green">FibonacciTest:</span> > [info] <span style="color:green">Single Cycle CPU</span> > [info] <span style="color:green">- should recursively calculate Fibonacci(10)</span> > ....... ### Modify the homework2's assembly code that can run in MyCPU #### rewite some instruction in my assembly code * change the [assembly code](https://github.com/y0y0alex/ca2023-lab3/blob/main/csrc/logclz.S) named `logclz.S` that `j ra` to `jal back1` * Change the assembly code that instruction `mv` into `addi` * Modify the Makefile, add the `logclz.asmbin` ```clike=24 BINS = \ logclz.asmbin \ fibonacci.asmbin \ hello.asmbin \ mmio.asmbin \ quicksort.asmbin \ sb.asmbin \ ``` * After those modify you can use the `make update` at dir `ca2023-lab3/csrc` , it will generate the `.asmbin` file and update to dir `ca2023-lab3/src/main/resources` * that you already finish this step. ### Modify the CPUTest.scala * Code information * calculate the answer of $log_{2} 64$, the answer should be 6. * First I write the code like... ```scala=118 class logclz extends AnyFlatSpec with ChiselScalatestTester { behavior.of("Single Cycle CPU") it should "show the answer should be 6" in { test(new TestTopModule("logclz.asmbin")).withAnnotations(TestAnnotations.annos) { c => for (i <- 1 to 50) { c.clock.step() c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout } c.io.regs_debug_read_address.poke(10.U) c.io.regs_debug_read_data.expect(6.U) } } } ``` * As following, we only focus on `logclz` class execute, because other classes are already seccesss. * When I first run the test `sbt test` > [info] <span style="color:green">logclz:</span> > [info] <span style="color:green">Single Cycle CPU</span> > [info] <span style="color:red">- should show the answer should be 6 *** FAILED ***</span> > [info] <span style="color:red">io_regs_debug_read_data=0 (0x0) did not equal expected=6 (0x6) (lines in CPUTest.scala: 127, 121)(CPUTest.scala:127)</span> * At first, I thought it was an assembly code syntax error. But that not true. * After I study about `c.clock.step()` function, I discuss that is the reason I fail. I don't give it enough time to run. * After I change the time I give... ```scala=118 class logclz extends AnyFlatSpec with ChiselScalatestTester { behavior.of("Single Cycle CPU") it should "show the answer should be 6" in { test(new TestTopModule("logclz.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.regs_debug_read_address.poke(10.U) c.io.regs_debug_read_data.expect(6.U) } } } ``` * run the test > [info] <span style="color:green">logclz:</span> > [info] <span style="color:green">Single Cycle CPU</span> > [info] <span style="color:green">- should show the answer should be 6</span> * Some point we should focus on... 1. class name `logclz`. 2. TestTopModule() you need to insert thet you make call `logclz.asmbin`. 3. `for (i <- 1 to 50000)` I use the number `50000`, you can adjust the number that fit your code. 4. `c.io.regs_debug_read_address.poke(10.U)` the number `10` is address of register. My code store the final answer in register `a0` (you can see the address in Ripes that used in homework1). 5. `c.io.regs_debug_read_data.expect(6.U)` 6 is the value you expect. * Show the whole `sbt test` > [info] welcome to sbt 1.9.7 (Ubuntu Java 11.0.20.1) > [info] loading settings for project ca2023-lab3-build from plugins.sbt ... > [info] loading project definition from /home/ca/ca2023-lab3/project > [info] loading settings for project root from build.sbt... > [info] set current project to mycpu (in build file:/home/ca/ca2023-lab3/) > [info] compiling 1 Scala source to /home/ca/ca2023-lab3/target/scala-2.13/test-classes ... > [info] <span style="color:green">ExecuteTest:</span> > [info] <span style="color:green">Execution of Single Cycle CPU</span> > [info] <span style="color:green">- should execute correctly</span> > [info] <span style="color:green">QuicksortTest:</span> > [info] <span style="color:green">Single Cycle CPU</span> > [info] <span style="color:green">- should perform a quicksort on 10 numbers</span> > [info] <span style="color:green">InstructionFetchTest:</span> > [info] <span style="color:green">InstructionFetch of Single Cycle CPU</span> > [info] <span style="color:green">- should fetch instruction</span> > [info] <span style="color:green">InstructionDecoderTest:</span> > [info] <span style="color:green">InstructionDecoder of Single Cycle CPU</span> > [info] <span style="color:green">- should produce correct control signal</span> > [info] <span style="color:green">RegisterFileTest:</span> > [info] <span style="color:green">Register File of Single Cycle CPU</span> > [info] <span style="color:green">- should read the written content</span> > [info] <span style="color:green">- should x0 always be zero</span> > [info] <span style="color:green">- should read the writing content</span> > [info] <span style="color:green">ByteAccessTest:</span> > [info] <span style="color:green">Single Cycle CPU</span> > [info] <span style="color:green">- should store and load a single byte</span> > [info] <span style="color:green">logclz:</span> > [info] <span style="color:green">Single Cycle CPU</span> > [info] <span style="color:green">- should show the answer should be 6</span> > [info] <span style="color:green">FibonacciTest:</span> > [info] <span style="color:green">Single Cycle CPU</span> > [info] <span style="color:green">- should recursively calculate Fibonacci(10)</span> > [info] <span style="color:blue">Run completed in 1 minute, 3 seconds.</span> > [info] <span style="color:blue">Total number of tests run: 10</span> > [info] <span style="color:blue">Suites: completed 8, aborted 0</span> > [info] <span style="color:blue">Tests: succeeded 10, failed 0, canceled 0, ignored 0, pending 0</span> > [info] <span style="color:green">All tests passed.</span> > [<span style="color:green">success</span>] Total time: 75 s (01:15), completed 2023年11月22日 下午6:23:17 ## Check Waveform * use > $ WRITE_VCD=1 sbt test > $ gtkwave ### InstructionFetch ![image](https://hackmd.io/_uploads/BJzB0ii4p.png) * When `io.jump_flag = 1`, `io.instruction_address` will be set that instruction want to jump `0000100c` -> `00001000` . ### InstructionDecode ![image](https://hackmd.io/_uploads/rJyiJhiV6.png) * We can see the `opcode` = `23`, so that we check the code. This instruction is `aupic`, it is L type. ### Execute ![image](https://hackmd.io/_uploads/H1W7bniV6.png) * When `aluop1_source` become True, so we can see `io.instruction_address` is op1. ## Verilator ### make verilator > sbt "runMain board.verilator.VerilogGenerator" > [info] welcome to sbt 1.9.7 (Ubuntu Java 11.0.20.1) > [info] loading settings for project ca2023-lab3-build from plugins.sbt ... > [info] loading project definition from /home/ca/ca2023-lab3/project > [info] loading settings for project root from build.sbt ... > [info] set current project to mycpu (in build file:/home/ca/ca2023-lab3/) > [info] running board.verilator.VerilogGenerator > [<span style="color:green">success</span>] Total time: 10 s, completed 2023年11月23日 上午1:05:15 ### ./run-verilator.sh -instruction src/main/resources/logclz.asmbin > -time 10000 > -memory 1048576 > -instruction src/main/resources/logclz.asmbin > [-------------------->] 100% ### hexdump src/main/resources/mul_clz.asmbin | head -1 > 0000000 0297 0000 8293 1842 a503 0002 a583 0042 ![image](https://hackmd.io/_uploads/ByMivnsNp.png) * You can see `0297 0000` and the value of `mem_io_instruction` are same. * So the above waveform can show the instruction is correct.