# Lab3: Construct RISC-V CPU with Chisel ## Cheat Sheet https://github.com/freechipsproject/chisel-cheatsheet/releases/latest/download/chisel_cheatsheet.pdf https://gist.github.com/heathermiller/2ab9ef36910fdfdd20e9 https://www.aboutrv.com/tools/disassembler?code=MHgwZDg3ZjA1NwpjMiAyMCAyNyA3Mw%3D%3D https://github.com/freechipsproject/diagrammer?tab=readme-ov-file ``` println(getVerilog(new MyOperatorsTwo)) ``` Debug in Chisel ```scala class PrintingModule extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) io.out := io.in printf("Print during simulation: Input is %d\n", io.in) // chisel printf has its own string interpolator too printf(p"Print during simulation: IO is $io\n") println(s"Print during generation: Input is ${io.in}") } test(new PrintingModule ) { c => c.io.in.poke(3.U) c.clock.step(5) // circuit will print println(s"Print during testing: Input is ${c.io.in.peek()}") } ``` Set the environment variable WRITE_VCD to 1, waveform files will be generated. ```shell $ WRITE_VCD=1 sbt test ``` https://luplab.gitlab.io/rvcodecjs/ myCpu need to load the instruction into mem. myCpu will start instuction decode until the `instruction_valid` signal goes `high` I can start to trace the instuction after `instruction_valid` signal goes `high` >TestTopModule orchestration: Before load_finished: ROMLoader writes to Memory, CPU receives zero data After load_finished: CPU executes from Memory, ROMLoader is idle ## Environment Preparation ```shell $ java -version openjdk version "1.8.0_462" OpenJDK Runtime Environment (Temurin)(build 1.8.0_462-b08) OpenJDK 64-Bit Server VM (Temurin)(build 25.462-b08, mixed mode) $ javac -version javac 1.8.0_462 ``` use Surfer to view waveform ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh cargo install --git https://gitlab.com/surfer-project/surfer.git --tag v0.4.0 surfer ``` ## Chisel Bootcamp ```bash docker run -it --rm -p 8888:8888 ucbbar/chisel-bootcamp ``` {%preview https://hackmd.io/Ds_csJawToSNGAhhpHJtWQ %} ## 0-minimal RISC-V CPU jit.asmbin from Quiz-3 Problem A disassembling from a bin ```shell $ riscv-none-elf-objdump -D -b binary -m riscv jit.asmbin 0: 00000297 auipc t0,0x0 4: 03428293 addi t0,t0,52 # 0x34 8: 00000317 auipc t1,0x0 c: 02430313 addi t1,t1,36 # 0x2c 10: 0002a383 lw t2,0(t0) 14: 00732023 sw t2,0(t1) 18: 0042a383 lw t2,4(t0) 1c: 00732223 sw t2,4(t1) 20: 00000297 auipc t0,0x0 24: 00c28293 addi t0,t0,12 # 0x2c 28: 000280e7 jalr t0 2c: 00000013 nop 30: 00000013 nop 34: 02a00513 li a0,42 38: 00008067 ret ``` source code ```c .data jit_instructions: .word 0x02A00513 # addi a0, zero, 42 .word 0x00008067 # jalr zero, ra, 0 (ret) .text .globl _start _start: la t0, jit_instructions la t1, jit_code_buffer lw t2, 0(t0) sw t2, 0(t1) lw t2, 4(t0) sw t2, 4(t1) la t0, jit_code_buffer jalr ra, t0, 0 jit_code_buffer: .word 0x00000013 # nop (placeholder, will be overwritten) .word 0x00000013 # nop (placeholder, will be overwritten) ``` ## Fully understand and verify 0-minimal carefully Comments marked with `CA25: Exercise` in the source indicate TODOs that must be completed as part of the exercises. ## 1-signle-cycle ``` sbt "project singleCycle" "testOnly *InstructionDecoderTest" ``` ### CA25: Exercise 4 ```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) } ``` `beq ra(x1), sp(x2), pc + 2` ### CA25: Exercise 7 RV32I provides a 32-bit address space that is byte-addressed[^riscv-unprivileged.pdf] `log2Up` is https://www.chisel-lang.org/api/latest/chisel3/util/log2Up$.html ```scala val EntryAddress = 0x1000.U(Parameters.AddrWidth) ``` ```shell $ riscv-none-elf-objdump -D -b binary -m riscv 1-single-cycle/src/main/resources/sb.asmbin Disassembly of section .data: 0000000000000000 <.data>: 0: 00400513 li a0,4 4: deadc2b7 lui t0,0xdeadc 8: eef28293 addi t0,t0,-273 # 0xffffffffdeadbeef c: 00550023 sb t0,0(a0) 10: 00052303 lw t1,0(a0) 14: 01500913 li s2,21 18: 012500a3 sb s2,1(a0) 1c: 00052083 lw ra,0(a0) 20: 0000006f j 0x20 ``` Becasue the Rom EntryAddress is `0x1000` . Need to Modify the Ripes Compiler Setting `.text section start address` to `0x00001000` Result in Ripes Register x1(ra) is 0x000015ef x5(t0) is 0xdeadbeef x6(t1) is 0x000000ef x10(a0) is 0x00000004 x18(s2) is 0x00000015 Memory | Address | Byte 0 | Byte 1 | Byte 2 | Byte3 | | ---------- | ------ |:------:|:------:|:-----:| | 0x00000004 | 0xef | 0x15 | 0x00 | 0x00 | ```shell WRITE_VCD=1 sbt "project singleCycle" "testOnly *ByteAccessTest" ``` ```scala writeData := data(7, 0) << (mem_address_index << 3) //Left shift by (mem_address_index * 8) bits ``` From `common/src/main/scala/riscv/core/RegisterFile.scala` says > // Map x1-x31 to indices 0-30 in physical storage I can use the surfer to grab the waveform in `cpu-regs` `register_0[31:0]` is x1 ```scala c.io.regs_debug_read_address.poke(6.U) // is reg X6 ``` The Chisel Resiger Final Result is | wave form name | Rgister | alias | Value | |:-----------------:|:-------:|:-----:|:----------:| | register_0[31:0] | x1 | ra | 0x000015ef | | register_4[31:0] | x5 | t0 | 0xdeadbeef | | register_5[31:0] | x6 | t1 | 0x000000ef | | register_9[31:0] | x10 | a0 | 0x00000004 | | register_17[31:0] | x18 | s2 | 0x00000015 | **The Chisel MyCpu register is identical to the Ripes register.** ### Integration Tests Fibonacci(10) program ```shell $ sbt "project singleCycle" "testOnly *FibonacciTest" ``` Use Ripes to Load the `Flat Binary` Load at `0x00001000` Entry Point at `0x00001000` Memory Result is 55 0x37 at `0x00000004` Use Ripes to Figure witch instruction to save the result in memory ` 10f4: 00f4a023 sw x15 0 x9` recap: sw rs2, imm(rs1) imm is 0 x15(a5) is `fib(10) result` x9(s1) is memory address `0x00000004` ```shell $ WRITE_VCD=1 sbt "project singleCycle" "testOnly *FibonacciTest" $ surfer 1-single-cycle/test_run_dir/Single_Cycle_CPU__Integration_Tests_should_correctly_execute_recursive_Fibonacci10_program/TestTopModule.vcd ``` ```shell $ riscv-none-elf-objdump -D -b binary -m riscv 1-single-cycle/src/main/resources/fibonacci.asmbin # Display Register in xN, no-aliases is no pseudo $ riscv64-unknown-elf-objdump -M numeric,no-aliases -D -b binary -m riscv 1-single-cycle/src/main/resources/fibonacci.asmbin ``` I Found These Instructions used from dissabmley code. I will expand the `singlecycle/ExecuteTest.scala` to test these Instructions. * pseudo instruction - li (addi or lui & addi) - mv(addi) - ret(jalr x0, ra, 0) - j offset (jal x0, offset) * R Type - [x] add - [x] Decode - [x] Execute * I Type - [x] addi - [x] Decode - [x] Execute - [x] lw - [x] Decode - [x] MemoryAccess * S Type - [x] sw - [x] Decode - [x] MemoryAccess * B Type - [x] beq - [x] Decode - [x] Execute - [x] bgeu - [x] Decode - [x] Execute - [x] bne - [x] Decode - [x] Execute * J Type - [x] jal - [x] Deocde - [x] Execute - [ ] WriteBack (Need to Check return address is pc + 4) * U Type - [x] lui - [x] Decode - [x] WriteBack - [x] auipc - [x] Decode - [x] WriteBack ### RISCOF Compliance Testing All Pass in Signle Cycle CPU ## 2-mmio-trap ```bash WRITE_VCD=1 sbt "project mmioTrap" "testOnly *C LINTCSRTest" ``` ```bash $ sbt "project mmioTrap" test [info] Tests: succeeded 9, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. ``` ```bash $ make compliance ``` Result : 119Passed, 0Failed ## 3-pipeline ```bash $ sbt "project pipeline" "testOnly *PipelineRegisterTest" ``` ### Pipeline in mind IF → ID → EX → MEM → WB ### Phase 2: Data Forwarding (Exercises 17, 18) Test Fail edit `PipelineConfigs.scala` to remain the `FiveStageFinal` ```bash $ WRITE_VCD=1 sbt "project pipeline" "testOnly *PipelineProgramTest" ``` - [x] Forwarding.scala - [x] IF2ID.scala - [x] Control.scala ```scala // a) Jump dependency: ADD x1, x2, x3 [EX]; JALR x0, x1, 0 [ID] → stall // b) Load-use: LW x1, 0(x2) [EX]; ADD x3, x1, x4 [ID] → stall // c) Load-branch: LW x1, 0(x2) [EX]; BEQ x1, x4, label [ID] → stall ``` ## Learn I realize the fibonacci.asmbin will detect the ALU, branches, memory related problems. ### riscv-none-elf-objdump https://man7.org/linux/man-pages/man1/objdump.1.html riscv-none-elf-objdump decode branch or jump address to add the pc address pc @ 00f71663 deocde result is `bne x14, x15, 12` ``` 80: 00f71663 bne a4,a5,0x8c ``` https://github.com/bminor/binutils-gdb/blob/7596c998e157149df7e5b0b31580e3707d0b5000/opcodes/riscv-dis.c#L387 `--disassembler-color=extended` Enables or disables the use of colored syntax highlighting in disassembly output ### riscv-privileged.pdf The standard RISC-V ISA sets aside a 12-bit encoding space (csr[11:0]) for up to 4,096 CSRs. By convention, the upper 4 bits of the CSR address (csr[11:8]) are used to encode the read and write accessibility of the CSRs according to privilege level as shown in Table 3. The top two bits (csr[11:10]) indicate whether the register is read/write (00,01, or 10) or **read-only (11)**. The next two bits (csr[9:8]) encode the lowest privilege level that can access the CSR. ### Create a Pull Request Fix PipelineRegister.scala comment ### Run Large Program {%preview https://hackmd.io/OLeERomFSxatA_mUX--N6g %} ## Reference [^riscv-unprivileged.pdf]: riscv-unprivileged.pdf