# 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