---
# System prepended metadata

title: Assignment 3

---

<h1> Assignment 3: Your Own RISC-V CPU</h1>

contributed by <[`hsuhsuhs`](https://github.com/hsuhsuhs/NCKU-ca2025-mycpu)> 

---

[Toc]

## Environment Setup
This section outlines the procedure for setting up the Chisel development environment on macOS.
* To simulate hardware designs and view waveforms, Verilator and Surfer were installed using Homebrew.
 
  ```bash
  $ brew install verilator surfer
  ```
  * Verilator: Used for compiling Verilog (generated from Chisel) into a fast C++ cycle-accurate simulation model.
  * Surfer: A modern waveform viewer used to debug signals.   

* Setting up Scala and sbt
    * Clean Installation Existing versions of sbt or jenv were removed to prevent conflicts:
      ```bash
      $ brew uninstall sbt
      $ brew uninstall jenv
      ```
    * Installing SDKMAN and Java 11 SDKMAN was used to manage parallel versions of multiple Software Development Kits.
      ```bash
      # Install SDKMAN
      $ curl -s "https://get.sdkman.io" | bash
      $ source "$HOME/.sdkman/bin/sdkman-init.sh"

      # Install JDK 11 and sbt
      $ sdk install java 11.0.29-tem
      $ sdk install sbt
      ```
* Chisel Environment Verification
  ```bash
  $ git clone https://github.com/ucb-bar/chisel-tutorial
  $ cd chisel-tutorial
  $ git checkout release
  $ sbt run
  ```
### Chisel vs. Verilog 
Chisel acts as a Hardware Construction Language rather than a direct hardware description language like Verilog. The workflow involves writing Scala/Chisel code, which is then compiled into Verilog for use with standard EDA tools.

* Abstraction: Chisel uses object-oriented programming to generate complex Verilog logic programmatically.
* Design Enforcement: Chisel enforces modern digital design practices (e.g., synchronous logic) by limiting support for complex features like negative-edge triggering or unstructured multi-clock designs, which are often sources of bugs in Verilog.

#### Chisel Generation Flow

```mermaid
graph LR
    %% Nodes
    A[<b>Chisel Code</b><br/>High-Level Scala] 
    B(<b>Chisel Compiler</b><br/>sbt / FIRRTL)
    C[<b>Verilog File</b><br/>Hardware Description]
    D((<b>Simulation</b><br/>Verilator))
    E((<b>FPGA</b><br/>Bitstream))

    %% Edges / Connections
    A -->|1. Elaboration| B
    B -->|2. Generation| C
    C -->|3. Verification| D
    C -->|4. Synthesis| E

    %% Styling to highlight the Software vs Hardware boundary
    style A fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style B fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,stroke-dasharray: 5 5
    style C fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
```

### Riscof  (RISC-V Compatibility Framework)
To ensure strict adherence to the RISC-V Instruction Set Architecture specification, this project incorporates the RISCOF validation suite. Unlike ad-hoc unit tests which primarily verify functional existence, RISCOF performs rigorous architectural compliance testing to detect subtle deviations from the standard.
* The compliance testing process employs a differential testing strategy based on signature comparison.
    * The framework selects a suite of assembly tests (e.g., ADD-01.S, SLL-01.S) that cover all base instructions, including edge cases such as overflow, zero-extension, and signed comparisons.
    * RISCOF compares the signature file from the DUT against that of the Reference Model. Any discrepancy signifies a non-compliant implementation.   
    * Environment
    ![截圖 2025-12-10 晚上11.38.40](https://hackmd.io/_uploads/ryYXEGwMWx.png =300x)

* The compliance suite was executed using the project's build system command:
  ```bash
  $ make compliance
  ```
#### Adapting RISCOF for Apple Silicon (macOS)
While establishing the compliance testing framework, I encountered significant compatibility issues due to the architectural differences between the standard Lab environment and my local development environment.
* The Architecture Mismatch Issue
When attempting to run the standard make compliance command, the simulation failed immediately with a Java Native Interface  error.
  ```bash
  [error] Uncaught exception when running   riscv.compliance.ComplianceTest: 
  java.lang.UnsatisfiedLinkError: ... (mach-o file, but is an incompatible architecture (have 'x86_64', need  'arm64e' or 'arm64'))
  ```
  * My local JVM runs in native ARM64 mode. However, the simulation environment likely triggered via Python wrappers or default Verilator settings was attempting to link against x86_64 shared libraries. 

* System Reconfiguration
It became clear that a straightforward port was insufficient. I needed to force the build system to respect the local architecture and correct the path resolutions for the Python plugins. 
   * Step 1: I engineered a custom configuration file, such as `config-3-pipeline.ini`, to precisely define the paths for the DUTPlugin (mycpu) and ReferencePlugin (rv32emu). This bypassed the default Linux-centric paths

     ```TOML
     [RISCOF]
     ReferencePlugin=rv32emu
     ReferencePluginPath=rv32emu_plugin
     DUTPlugin=mycpu
     DUTPluginPath=mycpu_plugin
     ...
     ```
   * Step 2: The custom plugins were not visible to the default Python environment. I resolved this by manually exporting the `PYTHONPATH` to include the plugin directories before execution:
   
     ```bash
     export PYTHONPATH=$PROJECT_ROOT/tests/mycpu_plugin:$PROJECT_ROOT/tests/rv32emu_plugin:$PYTHONPATH
     ```
  *  Step 3: To automate this process and ensure reproducibility, I modified the `Makefile`. I created a target that explicitly passes the custom configuration and suite paths to the RISCOF runner, overriding the defaults.
     
     ```bash
     compliance:
     cd ../tests && $(RISCOF) run --config $(CONFIG) --   suite $(SUITE) --env $(ENV) --work-dir $(WORK)
     ```
* Executing via SBT
Instead of relying on make compliance which triggers the incompatible Python wrapper, I can now execute both basic unit tests and architectural compliance tests using a single command within each project directory:
  ```bash
  $ sbt test
  ```
    

## 0-minimal
In this section, I implemented a minimal RISC-V core designed specifically to pass the jit.asmbin test. This exercise demonstrates the concept of Just-In-Time compilation and self-modifying code, where the processor treats data written to memory as executable instructions.

### Implementation Strategy
Unlike a full RV32I processor, this minimal core only supports the 5 instructions required for the JIT test:

* **SW (Store Word)**: Used by the program to write new machine code instructions into memory at runtime.
* **LW (Load Word)**: Verifies memory reads.
* **JALR (Jump and Link Register)**: Critical for transferring control from the main program to the newly generated code in memory.
* **AUIPC & ADDI (Add upper Immediate to PC & Add Immediate )**: Used for address calculation and register manipulation.

### Test Verification
The test jit.asmbin writes a small code sequence to memory, jumps to it, and calculates a result. The expected return value in register a0 is 42.
* The testing environment is invoked using the standard sbt command line interface. This ensures consistency across different development stages from the minimal CPU to the pipelined version.

  ```bash
  $ sbt   
  [info] started sbt server
  [sbt:mycpu-root> project minimal
  [sbt:mycpu-minimal> test
  ```
  
* Test Output Analysis
  ```bash
  [info] JITTest:
  [info] Minimal CPU - JIT Test
  [info] - should correctly execute jit.asmbin and set a0 to 42
  [info] Run completed in 1 minute, 7 seconds.
  [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: 68 s 
  ```
### Use Surfer to view waveform
```bash
surfer trace.vcd
```
![截圖 2025-12-14 下午6.37.55](https://hackmd.io/_uploads/r1xeEzhf-l.png =480x)

  
## 1-single-cycle
This design implements the complete RV32I Base Integer Instruction Set, making it capable of executing general-purpose C programs.
### Architectural Overview
The processor adopts a classic single-cycle microarchitecture where every instruction completes its execution within one clock cycle ($CPI = 1$). The data path is structured into five distinct stages, connected via Chisel Bundles to ensure modularity:
![image](https://hackmd.io/_uploads/S1ricIEz-l.png)

* **Instruction Fetch (IF)**: Maintains the Program Counter (PC) and fetches instructions from the Instruction Memory. It handles sequential execution ($PC+4$) and updates the PC for jumps/branches.
* **Instruction Decode (ID)**: Decodes the 32-bit instruction to generate control signals (e.g., MemRead, MemWrite, RegWrite). It also reads operands from the Register File.
* **Execute (EX)**: Performs arithmetic/logical operations using the ALU and calculates branch target addresses.
* **Memory Access (MEM)**: Handles data exchange with Data Memory for Load (and Store ) instructions.
* **Write Back (WB)**: Selects the final result  and writes it back to the destination register.

### Implementation Strategy
RV32I Base Integer Instruction Set.
* Arithmetic and Logic Instructions: add, sub, slt, etc.
* Memory Access Instructions: lb, lw, sb, etc.
* Branch Instructions: beq, jar, etc.

### The issues I encounter and how I overcome them
1. During the initial testing of branch instructions, the CPU was jumping to incorrect, random addresses. 
   *  Unlike I-Type instructions, B-Type and J-Type instructions in RISC-V do not store immediate bits sequentially. The bits are scrambled to optimize the hardware decoder on the chip. 
   *  **Solution**: I implemented a precise bit-concatenation strategy using Chisel's `Cat` function to reconstruct the immediate values according to the ISA manual.
   
     ```scala
    // Resolving B-Type Scrambling: Reordering bits [31, 7, 30:25, 11:8]
    val immB = Cat(
       Fill(Parameters.DataBits - 13, instruction(31)),   // Sign Extension
       instruction(7),                                    // Bit 11
       instruction(30, 25),                               // Bits 10:5
       instruction(11, 8),                                // Bits 4:1
       0.U(1.W)                                           // LSB always 0
     )
     ```
2. The `BLT` instruction failed when comparing a negative number (e.g., -1) with a positive number (e.g., 5). The branch was not taken as expected. 
   *  In Chisel, `UInt` types are treated as unsigned by default. Therefore, -1 (`0xFFFFFFFF`) was interpreted as a very large positive number, making it greater than 5. 
   *  **Solution**: I modified the comparator logic to explicitly cast operands to `SInt` (Signed Integer) for `BLT` and `BGE` instructions, while keeping `BLTU` and `BGEU` as `UInt`.
   ```scala
   // Distinguishing Signed vs Unsigned Comparisons
   InstructionsTypeB.blt  -> (io.reg1_data.asSInt < io.reg2_data.asSInt), // Signed Cast
   InstructionsTypeB.bltu -> (io.reg1_data < io.reg2_data)                // Unsigned Keep
   ```
   
3. The `JALR` instruction occasionally caused misalignment exceptions or jumped to odd addresses.
   * According to the RISC-V specification, the target address of `JALR` is obtained by adding the 12-bit immediate to the register `rs1`, and then setting the least significant bit  to 0. My initial addition did not mask the LSB.
   * **Solution**: I used bit slicing and concatenation to force the LSB to zero, ensuring 2-byte alignment for instructions.
   ```scala
   // JALR Target Calculation: Force LSB to 0
   val jalrSum = io.reg1_data + io.immediate
   val jalrTarget = Cat(jalrSum(Parameters.DataBits - 1, 1), 0.U(1.W))
   ```
   
### Test Verification
The testing environment is invoked using the standard sbt command line interface. 
```bash
$ sbt   
[info] started sbt server
[sbt:mycpu-root> project singleCycle
[sbt:mycpu-singleCycle> test
```
#### Base Test
```bash
[info] InstructionDecoderTest:
[info] InstructionDecoder
[info] - should decode RV32I instructions and generate correct control signals
[info] ByteAccessTest:
[info] Single Cycle CPU - Integration Tests
[info] - should correctly handle byte-level store/load operations (SB/LB)
[info] InstructionFetchTest:
[info] InstructionFetch
[info] - should correctly update PC and handle jumps
[info] ExecuteTest:
[info] Execute
[info] - should execute ALU operations and branch logic correctly
[info] FibonacciTest:
[info] Single Cycle CPU - Integration Tests
[info] - should correctly execute recursive Fibonacci(10) program
[info] RegisterFileTest:
[info] RegisterFile
[info] - should correctly read previously written register values
[info] - should keep x0 hardwired to zero (RISC-V compliance)
[info] - should support write-through (read during write cycle)
[info] QuicksortTest:
[info] Single Cycle CPU - Integration Tests
[info] - should correctly execute Quicksort algorithm on 10 numbers
[info] Run completed in 22 seconds, 456 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: 25 s
```
#### Base Test + Compliance Test
```bash
[info] InstructionDecoderTest:

...

[info] ComplianceTest:
[info] MyCPU Compliance
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_1sc/rv32i_m/I/src/add-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/add-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_1sc/rv32i_m/I/src/addi-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/addi-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_1sc/rv32i_m/I/src/and-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/and-01.S
riscv-arch-test/riscv-test-suite/rv32i_m/I/src/xori-01.S

...
...

✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_1sc/rv32i_m/hints/src/fence-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/hints/src/fence-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_1sc/rv32i_m/hints/src/srl-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/hints/src/srl-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_1sc/rv32i_m/privilege/src/misalign1-jalr-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/privilege/src/misalign1-jalr-01.S

...

[info] QuicksortTest:
[info] Single Cycle CPU - Integration Tests
[info] - should correctly execute Quicksort algorithm on 10 numbers
[info] Run completed in 2 minutes, 25 seconds.
[info] Total number of tests run: 50
[info] Suites: completed 8, aborted 0
[info] Tests: succeeded 50, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 146 s
```

## 2-mmio-trap

### Architectural Overview
This project extends the microarchitecture to support RISC-V Privileged Architecture . The data path is significantly expanded to handle asynchronous events and external device communication through three key architectural additions:
* **Control and Status Registers (CSR)**: Implements a separate 4096-byte address space independent of the general-purpose register file. This module manages the processor's privilege state (e.g., `mstatus`, `mie`) and supports atomic Read-Modify-Write operations required by the ISA.
* **Core-Local Interrupt Controller (CLINT)**: Acts as the central nervous system for trap handling. It coordinates the transition between normal execution and exception handlers by managing context saving (`mepc`, `mcause`) and control flow redirection (`mtvec`) at instruction boundaries.
* **Memory-Mapped I/O (MMIO) Interface**: Modifies the memory stage to include an address decoder. This logic differentiates between standard memory access and peripheral device communication (Timer, UART, VGA) based on the high-order address bits.

### Implementation Strategy
The design incorporates the Zicsr Extension and system-level instructions necessary for embedded operating system support.
* **CSR Instructions (Zicsr)**: `csrrw`, `csrrs`, `csrrc` and their immediate variants. These ensure atomic updates to system status registers.
* **System & Trap Instructions**:
   * Trap Entry: `ecall` (Environment Call), `ebreak` (Breakpoint).
  * Trap Return: `mret` (Return from Machine-mode trap), restoring the PC and interrupt enable state.
* **Peripheral Integration**:
   * Timer: Configurable 32-bit counter with interrupt generation logic.
   * UART/VGA: Support for serial communication and graphical display output via memory-mapped addresses.

### The issues I encounter and how I overcome them
1. A critical race condition exists between software and hardware. The software might try to write to a CSR at the exact same cycle that a hardware interrupt occurs which requires the CLINT to save the mcause and update mstatus.
   * If both the CPU pipeline and the CLINT module assert write enables to the CSR file simultaneously, the hardware trap context could be corrupted by the software write, leading to a system crash or incorrect exception handling.
   * **Solution**: I implemented a Hardware Priority Logic within the CSR module. The CLINT's `direct_write_enable` signal is given strict precedence over the CPU's `reg_write_enable_id`.
    ```scala
    // Priority Arbitration: CLINT (Hardware) > CPU (Software)
    when(io.clint_access_bundle.direct_write_enable) {
    // Atomic hardware update for Trap Entry
      mstatus := io.clint_access_bundle.mstatus_write_data
      mepc    := io.clint_access_bundle.mepc_write_data
      mcause  := io.clint_access_bundle.mcause_write_data
    }.elsewhen(io.reg_write_enable_id) {
    // Software CSR instruction
      when(io.reg_write_address_id === CSRRegister.MSTATUS) {
         mstatus := io.reg_write_data_ex
      }
      // ... other CSRs
   }
    ```
    
2. When an interrupt occurred simultaneously with a Branch or Jump instruction, the PC sometimes updated to the Branch Target instead of the Trap Handler (`mtvec`).
   * **Solution**: I utilized a nested `Mux` structure to enforce this priority.
   ```scala
   // PC Update Priority: Interrupt > Jump/Branch > Sequential
   pc := Mux(
     io.interrupt_assert,
     io.interrupt_handler_address, // 1. Highest Priority: Interrupt Vector
     Mux(
         io.jump_flag_id,
         io.jump_address_id,       // 2. Control Flow
         pc + 4.U                  // 3. Sequential
     )
   )
   ```
3. The Store Byte instruction was writing to the correct address but shifting the data incorrectly, overwriting adjacent bytes in the 32-bit word.
   * Memory is word-aligned in hardware. Writing a byte to address `0x...1` means the data must be shifted to bits `[15:8]`, and only the second write strobe must be active.
   * **Solution**: I calculated the shift amount dynamically based on the lower 2 bits of the address (`mem_address_index`).
   ```scala
   // Dynamic Shifting for Store Byte
   val byteShiftAmount = Cat(mem_address_index, 0.U(3.W)) // equivalent to index * 8
   io.memory_bundle.write_strobe(mem_address_index) := true.B
   io.memory_bundle.write_data := io.reg2_data(Parameters.ByteBits - 1, 0) << (byteShiftAmount)
   ```
   
4. The `JALR` instruction occasionally caused address misalignment exceptions.
   * The RISC-V specification mandates that the target address of `JALR` must have its Least Significant Bit set to 0. A simple addition `rs1 + imm` is insufficient.
   * **Solution**: I implemented bit concatenation to forcibly clear bit 0.
   ```scala
   // Force LSB to 0
   val jalrTarget = Cat(jalrSum(Parameters.DataBits - 1, 1), 0.U(1.W))
   ```
   
### Test Verification
The testing environment is invoked using the standard sbt command line interface. 
```bash
$ sbt   
[info] started sbt server
[sbt:mycpu-root> project mmioTrap
[sbt:mycpu-mmio-trap> test
```
#### Base Test
```bash
[info] ByteAccessTest:
[info] [CPU] Byte access program
[info] - should store and load single byte
[info] CLINTCSRTest:
[info] [CLINT] Machine-mode interrupt flow
[info] - should handle external interrupt
[info] - should handle environmental instructions
[info] UartMMIOTest:
[info] [UART] Comprehensive TX+RX test
[info] - should pass all TX and RX tests
[info] ExecuteTest:
[info] [Execute] CSR write-back
[info] - should produce correct data for csr write
[info] FibonacciTest:
[info] [CPU] Fibonacci program
[info] - should calculate recursively fibonacci(10)
[info] TimerTest:
[info] [Timer] MMIO registers
[info] - should read and write the limit
[info] InterruptTrapTest:
[info] [CPU] Interrupt trap flow
[info] - should jump to trap handler and then return
[info] QuicksortTest:
[info] [CPU] Quicksort program
[info] - should quicksort 10 numbers
[info] Run completed in 23 seconds, 273 milliseconds.
[info] Total number of tests run: 9
[info] Suites: completed 8, aborted 0
[info] Tests: succeeded 9, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 24 s
```

#### Base Test + Compliance Test
```bash
[info] ByteAccessTest:
[info] [CPU] Byte access program

...

[info] ComplianceTest:
[info] MyCPU Compliance
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_2mmio/src/add-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/add-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_2mmio/src/addi-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/addi-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_2mmio/src/and-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/and-01.S

...
...

✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_2mmio/src/sub-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/sub-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_2mmio/src/sw-align-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/sw-align-01.S

...
...

[info] QuicksortTest:
[info] [CPU] Quicksort program
[info] - should quicksort 10 numbers
[info] Run completed in 6 minutes, 22 seconds.
[info] Total number of tests run: 128
[info] Suites: completed 9, aborted 0
[info] Tests: succeeded 128, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 382 s
```

### Nyancat VGA Display Demo
Command line
```bash
cd ~/chisel-tutorial/ca2025-mycpu/2-mmio-trap
make demo
```

Demo
```bash
cd .. && PATH=$HOME/.local/bin:$PATH sbt "project mmioTrap" "runMain board.verilator.VerilogGenerator"
[info] welcome to sbt 1.10.7 (Eclipse Adoptium Java 11.0.29)
[info] loading project definition from /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/project
[info] loading settings for project root from build.sbt...
[info] set current project to mycpu-root (in build file:/Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/)
[info] set current project to mycpu-mmio-trap (in build file:/Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/)
[info] running board.verilator.VerilogGenerator 
[success] Total time: 3 s, completed 2025年12月10日 下午11:31:30
cd verilog/verilator && verilator --trace --exe --cc sim.cpp Top.v ../../src/main/resources/vsrc/TrueDualPortRAM32.v \
		-Wno-WIDTHEXPAND -Wno-WIDTH \
		-CFLAGS "-DENABLE_SDL2 $(sdl2-config --cflags)" -LDFLAGS "$(sdl2-config --libs)" && \
		make -C obj_dir -f VTop.mk
- V e r i l a t i o n   R e p o r t: Verilator 5.042 2025-11-02 rev UNKNOWN.REV
- Verilator: Built from 0.000 MB sources in 0 modules, into 0.000 MB in 0 C++ files needing 0.000 MB
- Verilator: Walltime 0.002 s (elab=0.000, cvt=0.000, bld=0.000); cpu 0.001 s on 1 threads
make[1]: Nothing to be done for `default'.
🐱 Starting VGA demo with nyancat animation...
   Display: 640×480@72Hz with SDL2 visualization
   Program: nyancat.asmbin (12-frame nyancat animation)
   Note: Frame upload + animation takes significant time
   Duration: 500M cycles (~5 minutes, includes full animation)

cd verilog/verilator/obj_dir && ./VTop -vga -instruction ../../../src/main/resources/nyancat.asmbin -time 500000000
[SDL2] Window opened: 640x480 'VGA Display - MyCPU'
[SDL2] Press ESC or close window to stop simulation early
Simulation progress: 1%
Simulation progress: 2%
Simulation progress: 3%
Simulation progress: 4%
Simulation progress: 5%
 
...

Simulation progress: 94%
Simulation progress: 95%
Simulation progress: 96%
Simulation progress: 97%
Simulation progress: 98%
Simulation progress: 99%
Simulation progress: 100%

✅ Demo complete! You should have seen animated nyancat.
```
![截圖 2025-12-10 晚上11.31.45](https://hackmd.io/_uploads/SkbEXzwzWg.png =500x)

#### Proposed Compression Strategies
I inspected the first 100 lines of nyancat.asmbin to reverse-engineer the rendering logic.
```bash
$ hexdump -C src/main/resources/nyancat.asmbin | head -n 100
```
The binary is densely populated with the Opcode `0x23`, which corresponds to the RISC-V S-Type instruction.
```bash
00000000  37 01 40 00 ef 00 80 7a  6f 00 00 00 13 01 01 fd  |7.@....zo.......|
00000010  23 26 11 02 23 24 81 02  13 04 01 03 23 2e a4 fc  |#&..#$......#...|
00000020  23 2c b4 fc 23 2a c4 fc  23 26 04 fe 6f 00 00 03  |#,..#*..#&..o...|
00000030  83 27 c4 fe 03 27 84 fd  33 07 f7 00 83 27 c4 fe  |.'...'..3....'..|
00000040  83 26 c4 fd b3 87 f6 00  03 47 07 00 23 80 e7 00  |.&.......G..#...|
00000050  83 27 c4 fe 93 87 17 00  23 26 f4 fe 03 27 c4 fe  |.'......#&...'..|
00000060  83 27 44 fd e3 46 f7 fc  13 00 00 00 13 00 00 00  |.'D..F..........|
00000070  83 20 c1 02 03 24 81 02  13 01 01 03 67 80 00 00  |. ...$......g...|
00000080  13 01 01 fe 23 2e 11 00  23 2c 81 00 13 04 01 02  |....#...#,......|
00000090  23 26 a4 fe 23 24 b4 fe  83 27 c4 fe 03 27 84 fe  |#&..#$...'...'..|

...

```
This pattern confirms that the graphics are rendered via Software Bit-Banging. The CPU is fetching and executing a unique store instruction for nearly every pixel or pixel group. This explains the large binary size relative to the simple animation—the image data is encoded as instructions.
* **Proposed Solution**: To compress this program effectively, we must move away from Instruction-based Rendering. My proposal to implement a Hardware Sprite Controller is validated by this finding, as it would replace thousands of these `SW` instructions with simple metadata writes, drastically reducing both instruction count and binary size. 
  * Implementation Plan:
    * Sprite Management Unit : Integrate a dedicated logic unit within the VGA controller.
    * Store the static sprite bitmaps (cat, background) in a dedicated Read-Only Memory block, separate from the instruction memory.
    *  CPU only needs to write lightweight Metadata (Asset ID, X-Coordinate, Y-Coordinate) to specific MMIO registers to update the frame, instead of manually rewriting the frame buffer. 

## 3-pipeline
This project involves transforming the core into a Pipelined Architecture. The primary objective is to improve instruction throughput by overlapping the execution of multiple instructions. The design shares the same front-end (Fetch/Decode) as the previous implementations but introduces complex control logic to handle hazards inherent in parallel execution.

### Architectural Overview
To ensure architectural correctness while progressively optimizing performance, it implemented the pipeline in four distinct iterations, selectable via the implementation parameter in Top.scala:
* **ThreeStage (IF $\to$ ID $\to$ EX/MEM/WB)**: A minimal pipeline that folds the last three stages into one. It introduces the basics of control-flow redirection without the complexity of data hazards.
* **FiveStageStall (IF $\to$ ID $\to$ EX $\to$ MEM $\to$ WB)**:The classic 5-stage design. It handles Data Hazards (Read-After-Write) conservatively by using Interlocks. The control unit inserts "bubbles" (NOPs) to wait for data dependencies.
* **FiveStageForward**: An optimized version that implements Data Forwarding . It routes results from the MEM or WB stages directly back to the EX stage, significantly reducing the number of stall cycles required.
* **FiveStageFinal**: The complete integration which combines forwarding with robust Control Hazard handling (Flushing) and the Core Local Interrupt Controller to support exceptions and interrupts.

### Hazard Management
* **Structural Hazards**: Structural hazards occur when multiple stages compete for the same resource.
* **Data Hazards**: Read-After-Write (RAW), Write-After-Write (WAW), Write-After-Read (WAR)
* **Control Hazards**: Branch and Jump instructions determine the next PC in the EX stage, but by then, the IF and ID stages have already fetched incorrect instructions.
* **Integration with Hazard Unit**
  * Extended the pipeline registers (`ID2EX`, `EX2MEM`) to carry control signals (`RegWrite`, `MemRead`) so the hazard unit can observe the state of instructions in flight.
  * Running `make sim` to inspect the waveforms to confirm that:
    * Load-Use Hazard: Correctly triggers a 1-cycle stall even in the forwarding design since memory data is not yet available.
    * Branch Misprediction: Correctly flushes the two following instructions.


```mermaid
graph LR
    %% --- define ---
    classDef stage fill:#f0f4c3,stroke:#827717,stroke-width:2px,font-weight:bold,rx:5,ry:5;
    classDef component fill:#e3f2fd,stroke:#1565c0,stroke-width:1px,rx:3,ry:3;
    classDef memory fill:#fff3e0,stroke:#e65100,stroke-width:1px,rx:3,ry:3;
    classDef pipeReg fill:#cfd8dc,stroke:#455a64,stroke-width:2px,stroke-dasharray: 5 5;

    %% ---  Instruction Fetch (IF) ---
    subgraph IF [Stage 1: Instruction Fetch]
        direction TB
        PC(Program Counter)
        IMEM(Instruction Memory)
    end

    %% ---  Instruction Decode (ID) ---
    subgraph ID [Stage 2: Decode]
        direction TB
        RegFile(Register File)
        CtrlUnit(Control Unit)
    end

    %% ---  Execute (EX) ---
    subgraph EX [Stage 3: Execute]
        direction TB
        ALU(ALU)
    end

    %% ---  Memory Access (MEM) ---
    subgraph MEM [Stage 4: Memory]
        direction TB
        DMEM(Data Memory)
    end

    %% ---  Write Back (WB) ---
    subgraph WB [Stage 5: Write Back]
        direction TB
        WBMux(Write Back Mux)
    end

    %% 
    PC --> IMEM
    IMEM --> RegFile
    RegFile --> ALU
    ALU --> DMEM
    DMEM --> WBMux
    WBMux --> RegFile

    
    class IF,ID,EX,MEM,WB stage;
    class PC,RegFile,CtrlUnit,ALU,WBMux component;
    class IMEM,DMEM memory;
```

### The issues I encounter and how I overcome them
1. The Forwarding Unit prioritizes the most recent correct data available in the pipeline registers (`EX/MEM` or `MEM/WB`) and routes it directly to the EX stage's ALU inputs, bypassing the Register File access.
* Forwarding Priority Logic：
The forwarding unit resolves data hazards by selecting the most recent value available in the pipeline. The priority logic is summarized below:

  | Forwarding Path | Priority | Condition | `reg_forward_ex` Setting |
  | :--- | :--- | :--- | :--- |
  | **MEM $\to$ EX** | **High** | `RegWriteEnable_MEM` AND `rd_MEM == rs_EX` AND `rd_MEM != x0` | `ForwardFromMEM` |
  | **WB $\to$ EX** | **Low** | `RegWriteEnable_WB` AND `rd_WB == rs_EX` AND `rd_WB != x0` | `ForwardFromWB` |

* **solution**
  ```scala
   when(io.reg_write_enable_mem && (io.rd_mem === io.rs1_ex) && io.rd_mem =/= 0.U) {
       io.reg1_forward_ex := ForwardingType.ForwardFromMEM
   }.elsewhen(io.reg_write_enable_wb && (io.rd_wb === io.rs1_ex) && io.rd_wb =/= 0.U) {
       io.reg1_forward_ex := ForwardingType.ForwardFromWB
   }.otherwise {
       io.reg1_forward_ex := ForwardingType.NoForward
   }
  ```






### Test Verification
The testing environment is invoked using the standard sbt command line interface. 
```bash
$ sbt   
[info] started sbt server
[sbt:mycpu-root> project pipeline
[sbt:mycpu-pipeline> test
```
#### Base Test
```bash
[info] PipelineProgramTest:
[info] Three-stage Pipelined CPU
[info] - should calculate recursively fibonacci(10)
[info] - should quicksort 10 numbers
[info] - should store and load single byte
[info] - should solve data and control hazards
[info] - should handle all hazard types comprehensively
[info] - should handle machine-mode traps
[info] Five-stage Pipelined CPU with Stalling
[info] - should calculate recursively fibonacci(10)
[info] - should quicksort 10 numbers
[info] - should store and load single byte
[info] - should solve data and control hazards
[info] - should handle all hazard types comprehensively
[info] - should handle machine-mode traps
[info] Five-stage Pipelined CPU with Forwarding
[info] - should calculate recursively fibonacci(10)
[info] - should quicksort 10 numbers
[info] - should store and load single byte
[info] - should solve data and control hazards
[info] - should handle all hazard types comprehensively
[info] - should handle machine-mode traps
[info] Five-stage Pipelined CPU with Reduced Branch Delay
[info] - should calculate recursively fibonacci(10)
[info] - should quicksort 10 numbers
[info] - should store and load single byte
[info] - should solve data and control hazards
[info] - should handle all hazard types comprehensively
[info] - should handle machine-mode traps
[info] PipelineUartTest:
[info] Three-stage Pipelined CPU UART Comprehensive Test
[info] - should pass all TX and RX tests
[info] Five-stage Pipelined CPU with Stalling UART Comprehensive Test
[info] - should pass all TX and RX tests
[info] Five-stage Pipelined CPU with Forwarding UART Comprehensive Test
[info] - should pass all TX and RX tests
[info] Five-stage Pipelined CPU with Reduced Branch Delay UART Comprehensive Test
[info] - should pass all TX and RX tests
[info] PipelineRegisterTest:
[info] Pipeline Register
[info] - should be able to stall and flush
[info] Run completed in 1 minute, 13 seconds.
[info] Total number of tests run: 29
[info] Suites: completed 3, aborted 0
[info] Tests: succeeded 29, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 76 s
```

#### Base Test + Compliance Test
```bash
[info] PipelineProgramTest:
[info] Three-stage Pipelined CPU

...

[info] ComplianceTest:
[info] MyCPU Compliance
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_3pl/src/add-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/add-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_3pl/src/addi-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/addi-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_3pl/src/and-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/and-01.S

...
...

t/riscv-test-suite/rv32i_m/I/src/sra-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_3pl/src/srai-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/srai-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_3pl/src/srl-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/srl-01.S
✅ Test completed - signature: /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscof_work_3pl/src/srli-01.S/dut/DUT-mycpu.signature
[info] - should pass test /Users/hsuhsiaofan/chisel-tutorial/ca2025-mycpu/tests/riscv-arch-test/riscv-test-suite/rv32i_m/I/src/srli-01.S

...
...

[info] Five-stage Pipelined CPU with Reduced Branch Delay UART Comprehensive Test
[info] - should pass all TX and RX tests
[info] PipelineRegisterTest:
[info] Pipeline Register
[info] - should be able to stall and flush
[info] Run completed in 8 minutes, 40 seconds.
[info] Total number of tests run: 147
[info] Suites: completed 4, aborted 0
[info] Tests: succeeded 148, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 520 s

```

### [CA25: Exercise 21] Hazard Detection Summary and Analysis

#### Q1: Why do we need to stall for load-use hazards?
>A stall is needed for a load-use hazard because the result of the Load instruction is not available until the Memory (MEM) stage, but the subsequent instruction in the Instruction Decode (ID) stage needs to read it. Stalling delays the dependent instruction by one cycle, giving the load instruction enough time to forward the data.

#### Q2: What is the difference between "stall" and "flush" operations?
>* Stall resolves Data Hazards by freezing the PC/IF/ID and forcing a NOP into the ID/EX register.
>*  Flush  resolves Control Hazards by discarding an instruction fetched down the wrong path.

#### Q3: Why does jump instruction with register dependency need stall?
>JALR requires the target address (from r_s1) to be calculated in the ID stage. If r_s1 is being written by a prior instruction (Data Hazard), a stall is needed until the correct value can be forwarded to the ID stage to calculate the next PC.

#### Q4: In this design, why is branch penalty only 1 cycle instead of 2?
>The branch penalty is only 1 cycle because the design implements early branch resolution in the ID stage. By resolving in ID, only the single incorrect instruction in the IF/ID register needs to be flushed (discarded), resulting in a 1-cycle penalty.

#### Q5: What would happen if we removed the hazard detection logic entirely?
>The processor would fail to execute code correctly. Data Hazards would lead to incorrect computation results, and Control Hazards would lead to instructions from the wrong code path being executed.

#### Q6: Complete the stall condition summary:
>Stall is needed when:
>1. (EX stage condition) An instruction in ID reads the destination of a Load or JALR instruction currently in the EX stage.
>2. (MEM stage condition) A JALR instruction in ID reads the destination of a Load instruction currently in the MEM stage.
>
>Flush is needed when:
>1. (Branch/Jump condition) A Branch instruction in the ID stage evaluates to Taken when io.jump_flag is true.


## Modify the handwritten RISC-V assembly code in Homework2
To validate the pipelined CPU's performance in a realistic scenario, I ported the Tower of Hanoi assembly program (from Homework 2) to the RISC-V 5-stage pipeline environment. The primary objective was to eliminate pipeline stalls caused by Load-Use Hazards through software-level optimization.
### Code Optimization
The critical optimization was applied in the `display_move` section, where character data is loaded from memory and then decrypted.
```diff
display_move:
      add     x5, s4, s2      # Calculate FROM address
-     lbu     s8, 0(x5)       # Load FROM char
-     xor     s8, s8, x6      # [STALL] Dependency on s8
-     add     x7, s4, s3      # Calculate TO address
-     lbu     s9, 0(x7)       # Load TO char

+     # --- Optimized Sequence ---
+     add     x7, s4, s3      # Calculate TO address (Moved up)
+     lbu     s8, 0(x5)       # Load FROM char
+     lbu     s9, 0(x7)       # [FILL SLOT] Load TO char immediately
+                             # This instruction executes while s8 is being loaded.
+     xor     s8, s8, x6      # [NO STALL] s8 is now ready (Forwarding WB->EX)
```
By reordering, the second `lbu` fills the bubble required by the first `lbu`, effectively eliminating the stall cycle.

### Compilation ELF to Binary
I modified the existing Makefile in the csrc/ directory to support the mixed compilation of C and Assembly sources:
* the C entry wrapper was compiled using gcc with the Zicsr extension enabled:

  ```bash
  riscv64-unknown-elf-gcc -O0 -Wall -march=rv32i_zicsr -mabi=ilp32 -c -o main.o main.c
  ```
  * create `main.c`
   ```c
    // main.c
    extern void qz2_A_main(); 

    // An empty print function fools the linker.
    void print_hanoi_move(int disk, int from, int to) {}

    void _start() {
        qz2_A_main();
        while(1); // 
    }
   ```
* Assembly Compilation and Linking
  ```bash
  riscv64-unknown-elf-as -R -march=rv32i_zicsr -mabi=ilp32 -o hanoi.o hanoi.S
  riscv64-unknown-elf-ld -o hanoi.elf -T link.lds --oformat=elf32-littleriscv main.o hanoi.o
  ```
* `objcopy` was used to extract the text and data sections into the raw binary format:
  ```bash
  riscv64-unknown-elf-objcopy -O binary -j .text -j .data hanoi.elf hanoi.asmbin
  ```
### Test Integration
Extend Scala code to test the new program.
```scala
// src/test/scala/riscv/PipelineProgramTest.scala

it should "execute optimized Hanoi Tower" in {
  runProgram("hanoi.asmbin", cfg) { c =>
    // Disable timeout to accommodate the long running time of the algorithm
    c.clock.setTimeout(0)
    // Step for sufficient cycles to ensure completion
    c.clock.step(50000)
  }
}

```
### Test Verification
command
```bash
WRITE_VCD=1 sbt "project pipeline" "testOnly riscv.PipelineProgramTest"
```

#### Test 

```bash
[info] PipelineProgramTest:
[info] Three-stage Pipelined CPU
[info] - should execute optimized Hanoi Tower
...

[info] Five-stage Pipelined CPU with Stalling
[info] - should execute optimized Hanoi Tower
...

[info] Five-stage Pipelined CPU with Forwarding
[info] - should execute optimized Hanoi Tower
...

[info] Five-stage Pipelined CPU with Reduced Branch Delay
[info] - should execute optimized Hanoi Tower
...

[info] Run completed in 1 minute, 9 seconds.
[info] Total number of tests run: 28
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 28, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 74 s
```




#### reference 
[2025-arch-homework3](https://hackmd.io/@sysprog/2025-arch-homework3)
[Lab3: Construct a RISC-V CPU with Chisel](https://hackmd.io/@sysprog/B1Qxu2UkZx)