owned this note
owned this note
Published
Linked with GitHub
# Final Project: Extend Lab3 to comply with RV32IM and CSR
Status: In progress
> Contributed by <[Yu Jui Huang](https://github.com/DarrenHuang0411/RiscV-Single-Cycle-CPU-with-CSR.git)>
>
> Real-time Update in Notion <[Link](https://www.notion.so/Final-Project-Extend-Lab3-to-comply-with-RV32IM-and-CSR-38ce64073a214521bb8067b06b645ec9?pvs=21)>
>
# ****1. RISC-V Information****
## 1.1 **RV32I Instruction Format**
In the foundational RV32I Instruction Set Architecture (ISA), there exist four fundamental instruction formats denoted as R, I, S, and U. These formats are consistently 32 bits in length and necessitate alignment on a four-byte boundary within memory. If a branch or unconditional jump is executed and the target address is not aligned to a four-byte boundary, an instruction-address-misaligned exception is triggered. Notably, this exception is associated with the branch or jump instruction itself and not the target instruction. It's essential to emphasize that a conditional branch, when not taken, does not result in the generation of an instruction-address-misaligned exception.
The three formats are shown below:
- **R-Type (Register) Format:**
| funt7 | rs2 | rs1 | funct3 | rd | opcode |
| --- | --- | --- | --- | --- | --- |
| funt7 | rs2 | rs1 | funct3 | rd | 0110011 |
- **I-Type (Immediate) Format:**
| imm[11:0] | rs1 | funct3 | rd | opcode |
| --- | --- | --- | --- | --- |
| imm[11:0] | rs1 | funct3 | rd | 0110011 |
- S**-Type (Register) Format:**
| imm[11:5] | rs2 | rs1 | funct3 | rd | opcode |
| --- | --- | --- | --- | --- | --- |
| imm[11:5] | rs2 | rs1 | funct3 | rd | 0110011 |
## 1.2 **M standard extension**
### 1.2.1 **Integer Multiplication Instructions:**
- **`MUL rd, rs1, rs2`**: x[rd] = x[rs1] × x[rs2]
Multiplies the values in registers rs1 and rs2 and stores the result in register rd.
- **`MULH rd, rs1, rs2`**: x[rd] = (x[rs1] × x[rs2]) ≫XLEN (signed)
Computes the high bits of the signed product of the values in registers rs1 and rs2 and stores the result in register rd.
- **`MULHSU rd, rs1, rs2`**: x[rd] = (x[rs1](u) × x[rs2](s)) ≫XLEN(s)
Computes the high bits of the signed product of rs1 and the absolute value of rs2, storing the result in rd.
- **`MULHU rd, rs1, rs2`**: x[rd] = (x[rs1] × x[rs2]) ≫XLEN (unsigned)
Computes the high bits of the unsigned product of the values in registers rs1 and rs2 and stores the result in register rd.
### 1.2.2 **Integer Division Instructions:**
- **`DIV rd, rs1, rs2`**: x[rd] = x[rs1] ÷(s) x[rs2]
Divides the signed value in register rs1 by the signed value in register rs2 and stores the quotient in register rd.
- **`DIVU rd, rs1, rs2`**: x[rd] = x[rs1] ÷(u) x[rs2]
Divides the unsigned value in register rs1 by the unsigned value in register rs2, storing the quotient in register rd.
- **`REM rd, rs1, rs2`**: x[rd] = x[rs1] % (s) x[rs2]
Computes the remainder of the signed division of rs1 by rs2 and stores the result in rd.
- **`REMU rd, rs1, rs2`**: x[rd] = x[rs1] % (u) x[rs2]
Computes the remainder of the unsigned division of rs1 by rs2, storing the result in rd.
### 1.2.3 The Instruction Decoder of M Extension
| | funct7 | rs2 | rs1 | funct3 | rd | opcode |
| --- | --- | --- | --- | --- | --- | --- |
| (R Type) | 31-25 | 24-20 | 19-15 | 14-12 | 11-7 | 6-0 |
| MUL | 0000001 | - | - | 000 | - | 0110011 |
| MULH | 0000001 | - | - | 001 | - | 0110011 |
| MULHU | 0000001 | - | - | 011 | | 0110011 |
| MULHSU | 0000001 | - | - | 010 | - | 0110011 |
| DIV | 0000001 | - | - | 100 | - | 0110011 |
| DIVU | 0000001 | - | - | 101 | - | 0110011 |
| REM | 0000001 | - | - | 110 | - | 0110011 |
| REMU | 0000001 | - | - | 111 | - | 0110011 |
## 1.3 **Control and Status Register(CSR) & Core Local Interrupts(CLINT) Concept**
### 1.3.1 CSR
**Introduction**
Control and Status Registers (CSRs) are essential components in computer architecture that play a crucial role in managing and monitoring the behavior of a processor or a system.
**Purpose**
The primary purpose of CSRs is to provide a mechanism for the processor to communicate and interact with the rest of the system. They act as a bridge between the processor's internal components and external devices or the operating system. CSRs are used for configuration, control, and monitoring of various aspects of the processor's operation.
In the RISC-V architecture, the machine-level interrupt handling is a critical aspect of system design. This involves the careful design of Control and Status Registers (CSRs) and the Core Local Interruptor (CLINT) to efficiently manage interrupts and exceptions at the machine level. This section explores the key considerations and design principles when creating a CSR and CLINT focused on machine-level operations.
**CSR Register**
- **Machine Status (mstatus) Register**
Use to record various control and status related to “Machine state”.
It is a machine-level CSR that holds various status and control information about the machine mode.

**The Machine-mode status register (mstatus) for RV32.**
- **Machine Exception Program Counter(mepc) Register**
Use to ****hold the program counter value at the time of an exception or trap in machine mode.
- Machine Cause(**mcause) Register**
Use to hold a code or identifier that indicates the cause of an exception or interrupt.
- **“mtvec” Register**
Use to specify the base address of the trap vector table for machine-level interrupts.
- Relative **CSR Address extracted from Spec. Vol.II Page 8-10**
| Number | Privilege | Name | Description |
| --- | --- | --- | --- |
| 0x300 | MRW | mstatus | |
| 0x305 | MRW | mtvec | Machine trap-handler base address. |
| 0x341 | MRW | mepc | Machine exception program counter. |
| 0x342 | MRW | mcause | |
| 0xC00 | MRW | CycleL | |
| 0XC80 | MRW | CycleH | only in RV32 |
**CSR Operation**
- **CSRRW/CSRRWI:**
Read the value from a Control and Status Register (CSR) and write the contents of a general-purpose register into the CSR.
- **CSRRS/CSRRSI:**
Read the value from a CSR, perform a bitwise OR operation with the value in register rs1, and write the result back into the CSR.
- **CSRRC/CSRRCI:**
Read the value from a CSR, perform a bitwise AND operation with the complement (~) of the value in register rs1, and write the result back into the CSR.
- **MERT (Machine Exception Return):**
This instruction is used to return from an exception, updating the mstatus register:
- Update MIE (Machine Interrupt Enable) with the value of MPIE.
- Update the MPIE field's value to 1.
### 1.3.2 Core Local Interrupt (CLINT)
**Purpose**
CLINT provides a centralized facility for handling interrupts that are local to each processor core in a multi-core system.
**Interrupt Handling:**
- Manages local interrupts, which are specific to a particular core.
- Coordinates interrupt requests from various sources within the core.
**Timer Facilities:**
- Includes programmable timers for each core.
- Often used for implementing time-related functionalities, such as context switching and scheduling.
**Key Components:**
1. **Timer Registers:**
- The CLINT contains a timer that generates periodic interrupts.
- This timer, often referred to as the mtime (machine time) or mtimecmp (machine time compare), can be configured to generate interrupts at specified intervals.
2. **Interrupt Registers:**
- **`msip` (Machine Software Interrupt Pending):** Indicates whether a software interrupt is pending for the core.
- **`mtime` and `mtimecmp` interrupts:** Triggered when the machine time (**`mtime`**) exceeds the value in **`mtimecmp`**.
3. **Software Interrupts:**
- The CLINT also supports software-generated interrupts. Software running on the RISC-V processor can trigger these interrupts by writing to specific registers in the CLINT.
4. **Memory-Mapped Registers:**
- The CLINT exposes a set of memory-mapped registers that allow software and peripherals to interact with it. The most common registers include:
- *mtime:* Holds the current machine time.
- *mtimecmp:* Used to set the compare value for the timer interrupt.
5. **Interrupt Handling:**
- When a timer interrupt occurs or a software-generated interrupt is triggered, the CLINT signals the core by raising an interrupt. The processor then follows the standard interrupt handling procedure, which involves saving the current context and transferring control to the interrupt service routine (ISR).
The Core Local Interrupter (CLINT) is a part of the memory-mapped I/O (MMIO) space in RISC-V processors. It provides a mechanism for handling timer interrupts and software-generated interrupts at the core level. CLINT is typically used in systems with multiple RISC-V cores, and each core has its own instance of the CLINT.
**Operation:**
1. **Interrupt Generation:**
- External devices, software, or timers can generate interrupts specific to a core.
- Timer interrupts are generated when the machine time (**`mtime`**) surpasses the value set in **`mtimecmp`**.
2. **Interrupt Handling:**
- When an interrupt occurs, the core suspends its current execution and transfers control to the corresponding interrupt service routine (ISR).
- The ISR is a specific piece of code designed to handle the interrupt and perform necessary actions.
3. **Timer Functionality:**
- The programmable timer facilities assist in managing time-related events.
- Context switches, task scheduling, and other time-sensitive operations can be synchronized using CLINT's timer capabilities.
4. **Global Interrupt Controller:**
- Interacting with a global interrupt controller that manages interrupts across all cores.
# 2. Recap Lab3: Single cycle CPU
> lab 3 Link: [Assignment3: single-cycle RISC-V CPU - HackMD](https://hackmd.io/zCc-wifJRyOTjTD5DHOkhQ)
>
> Github Link: [DarrenHuang0411/NCKU_CA_2023 at Assignment-3 (github.com)](https://github.com/DarrenHuang0411/NCKU_CA_2023/tree/Assignment-3)
# 3. Single cycle CPU comply with RV32IM and CSR
## 3.1 Single cycle CPU Diagram

**Refer to [https://yatcpu.sysu.tech/tutorial/interrupt-and-exception/](https://yatcpu.sysu.tech/tutorial/interrupt-and-exception/)**
## 3.2 Revise the version including CSR
### 3.2.1 **Instruction Fetch**
Compared to Assignment 3, the Instruction Fetch needs to be enhanced to handle interrupt signals received in CSR. This involves the introduction of two key conditions: `interrupt_assert` and `interrupt_handler_address`.

- **interrupt_assert**:
This parameter signifies the assertion of an interrupt signal. When set, it indicates that an interrupt has occurred and needs attention. Proper handling of this signal is crucial for interrupt-driven processes.
- **interrupt_handler_address**:
This parameter denotes the address where the interrupt handler is located. Upon the assertion of an interrupt, the program counter is redirected to this address, initiating the execution of the corresponding interrupt handler routine. It plays a vital role in managing the flow of execution during interrupt scenarios.
```scala
class InstructionFetch extends Module {
val io = IO(new Bundle {
val jump_flag_id = Input(Bool())
val jump_address_id = Input(UInt(Parameters.AddrWidth))
val instruction_read_data = Input(UInt(Parameters.DataWidth))
val instruction_valid = Input(Bool())
val interrupt_assert = Input(Bool())
val interrupt_handler_address = Input(UInt(Parameters.AddrWidth))
val instruction_address = Output(UInt(Parameters.AddrWidth))
val instruction = Output(UInt(Parameters.InstructionWidth))
})
val pc = RegInit(ProgramCounter.EntryAddress)
when(io.instruction_valid) {
io.instruction := io.instruction_read_data
// Final project(InstructionFetch) begin
when(io.interrupt_assert){
pc := io.interrupt_handler_address
}
.elsewhen(io.jump_flag_id){
pc := io.jump_address_id
}
.otherwise{
pc := pc + 4.U
}
// lab3(InstructionFetch) end
}.otherwise {
pc := pc
io.instruction := 0x00000013.U
}
io.instruction_address := pc
```
### **3.2.2 Instruction Decoder**

- For M-extension
```scala
```
- **For CSR Control**
**`io.ex_csr_address`:**
- CSR-related instructions assigns the bits [31:20] of the input instruction (**`io.instruction`**) to the **`ex_csr_address`** signal.
**`io.ex_csr_write_enable`:**
- Write operation to a CSR should is enabled based on the opcode and funct3 field of the instruction.
- The condition **`(opcode === Instructions.csr)`** checks if the opcode corresponds to the CSR instruction, indicating a CSR operation is being performed.
- The second part of the condition checks the funct3 field against various CSR-related instruction types (**`csrrw`**, **`csrrs`**, **`csrrc`**, **`csrrwi`**, **`csrrsi`**, **`csrrci`**).
If the funct3 matches any of these values, the condition evaluates to true.
- If both conditions are true, **`io.ex_csr_write_enable`** is set to true, indicating that the CSR write operation is enabled.
```scala
val ex_csr_write_enable = Output(Bool())
val ex_csr_address = Output(UInt(Parameters.CSRRegisterAddrWidth))
```
```scala
io.ex_csr_address := io.instruction(31, 20)
io.ex_csr_write_enable := (opcode === Instructions.csr) &&
(funct3 === InstructionsTypeCSR.csrrw || funct3 === InstructionsTypeCSR.csrrs ||
funct3 === InstructionsTypeCSR.csrrc || funct3 === InstructionsTypeCSR.csrwri ||
funct3 === InstructionsTypeCSR.csrrsi || funct3 === InstructionsTypeCSR.csrrci
)
```
### 3.2.3 **Execute**
**Inputs:**
- **`funct3`**: The instruction that specifies the CSR operation (e.g., **`csrrw`**, **`csrrs`**, etc.).
- **`io.reg1_data`**: The data from register 1 (specified by **`rs1`** in the instruction).
- **`io.exe_read_data_csr`**: The data read from the CSR specified by the instruction.
- **`io.immediate`**: An immediate value from the instruction (if applicable).
**`MuxLookup` Function:**
- The **`MuxLookup`** function is used to perform a conditional selection based on the value of **`funct3`**. It acts like a multiplexer (**`Mux`**), selecting a value based on the given conditions.
**Conditions and Values:**
- For each CSR instruction type (**`csrrw`**, **`csrrs`**, etc.), there is a corresponding condition in the **`IndexedSeq`**. The selected value is determined based on the type of CSR instruction.
- **`InstructionsTypeCSR.csrrw`**:
Write the data from register 1 (**`io.reg1_data`**) to the CSR.
- **`InstructionsTypeCSR.csrrs`**:
Write the result of a bitwise OR between the data read from the CSR and the data from register 1.
- **`InstructionsTypeCSR.csrrc`**:
Write the result of a bitwise AND between the complement of the data from register 1 and the data read from the CSR.
- **`InstructionsTypeCSR.csrrwi`**:
Write the immediate value to the CSR.
- **`InstructionsTypeCSR.csrrsi`**:
Write the result of a bitwise OR between the data read from the CSR and the immediate value.
- **`InstructionsTypeCSR.csrrci`**:
Write the result of a bitwise AND between the complement of the immediate value and the data read from the CSR.
**Result:**
- The selected value based on the CSR instruction type is assigned to **`io.csr_write_data_exe`**. This data will be used for writing to the specified CSR in the subsequent stages of the processor pipeline.
```scala
io.csr_write_data_exe := MuxLookup(
funct3,
0.U,
IndexedSeq(
InstructionsTypeCSR.csrrw -> io.reg1_data,
InstructionsTypeCSR.csrrs -> (io.exe_read_data_csr | io.reg1_data),
InstructionsTypeCSR.csrrc -> (io.exe_read_data_csr & (~io.reg1_data)),
InstructionsTypeCSR.csrrwi -> io.immediate,
InstructionsTypeCSR.csrrsi -> (io.exe_read_data_csr | io.immediate),
InstructionsTypeCSR.csrrci -> (io.exe_read_data_csr & (~io.immediate))
)
)
```
### 3.2.4 **Write Back (WB)**
- **Usage of `csr_read_data` in `InstructionDecode`:**
- The `csr_read_data` signal is utilized in the `InstructionDecode` module as one of the potential sources for writing data to the registers (`regs_write_data`).
- The `MuxLookup` construct selects the appropriate data source based on the value of `io.regs_write_source`.
If `io.regs_write_source` corresponds to `RegWriteSource.CSR`, the value of `io.csr_read_data` is chosen as the `regs_write_data`.
```scala
package riscv.core
import chisel3._
import chisel3.util._
import riscv.Parameters
class WriteBack extends Module {
val io = IO(new Bundle() {
...
//final project
val csr_read_data = Input(UInt(Parameters.DataWidth))
//final end
})
io.regs_write_data := MuxLookup(
io.regs_write_source,
io.alu_result,
IndexedSeq(
RegWriteSource.Memory -> io.memory_read_data,
//final project (object in InstructionDecode)
RegWriteSource.CSR -> io.csr_read_data,
//final end
RegWriteSource.NextInstructionAddress -> (io.instruction_address + 4.U)
)
)
}
```
### 3.2.5 CSR (New)
```scala
package riscv.core
import chisel3._
import chisel3.util._
import riscv.Parameters
// CSRRegister get
object CSRRegister{
val MSTATUS = 0x300.U(Parameters.CSRRegisterAddrWidth)
val MIE = 0x304.U(Parameters.CSRRegisterAddrWidth)
val MTVEC = 0x305.U(Parameters.CSRRegisterAddrWidth)
val MSCRATCH = 0x340.U(Parameters.CSRRegisterAddrWidth)
val MEPC = 0x341.U(Parameters.CSRRegisterAddrWidth)
val MCAUSE = 0x342.U(Parameters.CSRRegisterAddrWidth)
val CycleL = 0xC00.U(Parameters.CSRRegisterAddrWidth)
val CycleH = 0XC80.U(Parameters.CSRRegisterAddrWidth)
}
class CSR extends Module {
val io = IO(new Bundle() {
val read_address_id = Input(UInt(Parameters.CSRRegisterAddrWidth))
val write_address_id = Input(UInt(Parameters.CSRRegisterAddrWidth))
val write_enable_id = Input(Bool())
val write_data_exe = Input(UInt(Parameters.DataWidth))
val exe_csrrd_csr = Output(UInt(Parameters.DataWidth))
val clint_csr_bundle = Flipped(new CSRDirectAccessBundle)
val debug_reg_read_address = Input(UInt(Parameters.CSRRegisterAddrWidth))
val debug_reg_read_data = Output(UInt(Parameters.DataWidth))
})
//initialization
val mstatus = RegInit(UInt(Parameters.DataWidth), 0.U)
val mie = RegInit(UInt(Parameters.DataWidth), 0.U)
val mtvec = RegInit(UInt(Parameters.DataWidth), 0.U)
val mscratch = RegInit(UInt(Parameters.DataWidth), 0.U)
val mepc = RegInit(UInt(Parameters.DataWidth), 0.U)
val mcause = RegInit(UInt(Parameters.DataWidth), 0.U)
val cycles = RegInit(UInt(64.W), 0.U)
// connect to preset register
val regLUT =
IndexedSeq(
CSRRegister.MSTATUS -> mstatus,
CSRRegister.MIE -> mie,
CSRRegister.MTVEC -> mtvec,
CSRRegister.MSCRATCH-> mscratch,
CSRRegister.MEPC -> mepc,
CSRRegister.MCAUSE -> mcause,
CSRRegister.CycleL -> cycles(31, 0),
CSRRegister.CycleH -> cycles(63, 32)
)
cycles := cycles+ 1.U
// If the pipeline and the CLINT are going to read and write the CSR at the same time, let the pipeline write first.
// This is implemented in a single cycle by passing (write_data_exe) to clint and writing the data from the CLINT to the CSR.
io.exe_csrrd_csr := MuxLookup(io.read_address_id, 0.U)(regLUT)
io.debug_reg_read_data := MuxLookup(io.debug_reg_read_address, 0.U)(regLUT)
io.clint_csr_bundle.mstatus := mstatus
io.clint_csr_bundle.mtvec := mtvec
io.clint_csr_bundle.mcause := mcause
io.clint_csr_bundle.mepc := mepc
when(io.clint_csr_bundle.direct_write_enable) {
mstatus := io.clint_csr_bundle.mstatus_write_data
mepc := io.clint_csr_bundle.mepc_write_data
mcause := io.clint_csr_bundle.mcause_write_data
}
.elsewhen(io.write_enable_id) {
when(io.write_address_id === CSRRegister.MSTATUS) {
mstatus := io.write_data_exe
}
.elsewhen(io.write_address_id === CSRRegister.MEPC) {
mepc := io.write_data_exe
}
.elsewhen(io.write_address_id === CSRRegister.MCAUSE) {
mcause := io.write_data_exe
}
}
when(io.write_enable_id) {
when(io.write_address_id === CSRRegister.MIE) {
mie := io.write_data_exe
}
.elsewhen(io.write_address_id === CSRRegister.MTVEC){
mtvec := io.write_data_exe
}
.elsewhen(io.write_address_id === CSRRegister.MSCRATCH) {
mscratch := io.write_data_exe
}
}
}
```
### 3.2.6 CLINT (New)
**How to operation**
**`interrupt_enable`:**
Extracts the MIE (Machine Interrupt Enable) bit from the **`mstatus`** CSR.
**Trap Handling:**
Checks if there is a pending interrupt (**`Interrupt_Flag`** is not None) and if interrupts are enabled.
If true, updates relevant CSR values (**`mstatus`**, **`mepc`**, **`mcause`**) based on the type of interrupt.
Sets **`direct_write_enable`** to true, asserts **`interrupt_assert`**, and sets **`interrupt_handler_address`** to **`mtvec`** from the CSR.
**MRET Handling:**
Checks if the current instruction is an MRET instruction.
If true, updates **`mstatus`**, **`mepc`**, and **`mcause`** using values from the CSR.
Sets **`direct_write_enable`** to true, asserts **`interrupt_assert`**, and sets **`interrupt_handler_address`** to **`mepc`** from the CSR.**Default Behavior:**If no interrupt or MRET, preserves the current CSR values.
Sets **`direct_write_enable`** to false, **`interrupt_assert`** to false, and **`interrupt_handler_address`** to 0.
```scala
package riscv.core
import chisel3._
import chisel3.util._
import riscv.Parameters
object InterruptStatus{
val None = 0x0.U(8.W)
val Timer0 = 0x1.U(8.W)
val Ret = 0xF.U(8.W)
}
object InterruptEntry{
val Timer0 = 0x4.U(8.W)
}
object InterruptState{
val Idle = 0x0.U
val SyncAssert = 0x1.U
val AsyncAssert = 0x2.U
val MRET = 0x3.U
}
object CSRState {
val Idle = 0x0.U
val Traping = 0x1.U
val Mret = 0x2.U
}
class CSRDirectAccessBundle extends Bundle{
val mstatus = Input(UInt(Parameters.DataWidth))
val mepc = Input(UInt(Parameters.DataWidth))
val mcause = Input(UInt(Parameters.DataWidth))
val mtvec = Input(UInt(Parameters.DataWidth))
val mstatus_write_data= Output(UInt(Parameters.DataWidth))
val mepc_write_data= Output(UInt(Parameters.DataWidth))
val mcause_write_data= Output(UInt(Parameters.DataWidth))
val direct_write_enable = Output(Bool())
}
class CLINT extends Module{
val io = IO(new Bundle {
val Interrupt_Flag = Input(UInt(Parameters.InterruptFlagWidth))
val Instruction = Input(UInt(Parameters.InstructionWidth))
val IF_Instruction_Address = Input(UInt(Parameters.AddrWidth))
val jump_flag = Input(Bool())
val jump_address = Input(UInt(Parameters.AddrWidth))
val interrupt_handler_address = Output(UInt(Parameters.AddrWidth))
val interrupt_assert = Output(Bool())
val csr_bundle = new CSRDirectAccessBundle
})
// mstatus(3) ==> MIE
val interrupt_enable = io.csr_bundle.mstatus(3)
// Trap: choose Sync/Async
val instruction_address = Mux(io.jump_flag, io.jump_address, io.IF_Instruction_Address + 4.U)
when(io.Interrupt_Flag =/= InterruptStatus.None && interrupt_enable){
io.csr_bundle.mstatus_write_data := io.csr_bundle.mstatus(31, 4) ## 0.U(1.W) ## io.csr_bundle.mstatus(2, 0)
io.csr_bundle.mepc_write_data := io.IF_Instruction_Address + 4.U
io.csr_bundle.mcause_write_data := Mux(io.Interrupt_Flag(0), 0x80000007L.U, 0x8000000BL.U)
io.csr_bundle.direct_write_enable := true.B
io.interrupt_assert := true.B
io.interrupt_handler_address := io.csr_bundle.mtvec
}
.elsewhen(io.Instruction === InstructionsRet.mret){
io.csr_bundle.mstatus_write_data := io.csr_bundle.mstatus(31, 4) ## io.csr_bundle.mstatus(7) ## io.csr_bundle.mstatus(2, 0)
io.csr_bundle.mepc_write_data := io.csr_bundle.mepc
io.csr_bundle.mcause_write_data := io.csr_bundle.mcause
io.csr_bundle.direct_write_enable := true.B
io.interrupt_assert := true.B
io.interrupt_handler_address := io.csr_bundle.mepc
}
.otherwise{
io.csr_bundle.mstatus_write_data := io.csr_bundle.mstatus
io.csr_bundle.mepc_write_data := io.csr_bundle.mepc
io.csr_bundle.mcause_write_data := io.csr_bundle.mcause
io.csr_bundle.direct_write_enable := false.B
io.interrupt_assert := false.B
io.interrupt_handler_address := 0.U
}
}
```
- The CLINT module monitors interrupt flags, handles traps and MRET instructions, and updates relevant CSRs accordingly.
- It provides information about the interrupt handler address and whether an interrupt is currently asserted.
- The **`direct_write_enable`** signal indicates whether a direct write to the CSRs is required based on the executed instruction.
- This design allows for interrupt handling and CSR updates in a RISC-V processor.
### 3.2.7 Timer (New)
**I/O Port Definition:**
- `Timer_interrupt_Flag`: An output indicating whether a timer interrupt is asserted.
- `debug_limit`: An output providing the current value of the timer limit for debugging purposes.
- `debug_enabled`: An output indicating whether the timer is currently enabled for debugging.
**Register Declarations:**
- Three registers (`count`, `limit`, and `enabled`) are declared to maintain the state of the timer.
- `count`: Maintains the current count of the timer.
- `limit`: Represents the limit at which the timer resets.
- `enabled`: Indicates whether the timer is currently enabled.
**Debug Outputs:**
- `debug_limit` and `debug_enabled` are set to the values of `limit` and `enabled`, respectively. These are provided for debugging purposes.
**Timer Logic:**
- The timer logic is enclosed within the `when` and `otherwise` blocks.
- When the timer is enabled (`enabled` is true), the `count` is incremented. If the count reaches the `limit`, the count is reset to 0, and the `Timer_interrupt_Flag` is asserted.
- If the timer is not enabled, `Timer_interrupt_Flag` is set to false.
```scala
package peripheral
import chisel3._
import chisel3.util._
import riscv.Parameters
class Timer extends Module {
val io = IO(new Bundle {
val bundle = new RAMBundle
val Timer_interrupt_Flag = Output(Bool())
val debug_limit = Output(UInt(Parameters.DataWidth))
val debug_enabled = Output(Bool())
})
val count = RegInit(0.U(32.W))
val limit = RegInit(100000000.U(32.W))
val enabled = RegInit(true.B)
io.debug_limit := limit
io.debug_enabled := enabled
//final
//finish the read-write for count,limit,enabled. And produce appropriate Timer_interrupt_Flag
when(enabled){
count := count + 1.U
when(count === limit){
count := 0.U
io.Timer_interrupt_Flag := true.B
}
.otherwise{
io.Timer_interrupt_Flag := false.B
}
}
.otherwise{
io.Timer_interrupt_Flag := false.B
}
}
```
### 3.2.8 CPU
```bash
//final project(csr i/o)
val csr_regs = Module(new CSR)
val clint = Module(new CLINT)
//final end
....
//final project(clint interrupt assert)
IF.io.interrupt_assert := clint.io.interrupt_assert
IF.io.interrupt_handler_address := clint.io.interrupt_handler_address
//final end
....
//final project csr()
csr_regs.io.read_address_id := id.io.ex_csr_address
csr_regs.io.write_address_id := id.io.ex_csr_address
csr_regs.io.write_enable_id := id.io.ex_csr_write_enable
csr_regs.io.write_data_exe := ex.io.csr_write_data_exe
//csr_regs.io.exe_csrrd_csr :=
csr_regs.io.clint_csr_bundle <> clint.io.csr_bundle
//final end
//final project clint
clint.io.Interrupt_Flag := io.Interrupt_Flag
clint.io.IF_Instruction_Address := IF.io.instruction_address
clint.io.Instruction := IF.io.instruction
clint.io.jump_address := ex.io.if_jump_address
clint.io.jump_flag := ex.io.if_jump_flag
//final end
```
### 3.2.9 CPUBundle
The `CPUBundle` plays a crucial role in establishing a communication interface between the central processing unit (CPU) and peripheral devices, with a primary focus on memory interactions. This interface serves as the conduit for the exchange of data, allowing the CPU to interact seamlessly with various external components.
- **Communication Interface:**
- The `CPUBundle` serves as the bridge that facilitates the communication flow between the CPU and peripheral devices, especially memory modules. It defines the signals and connections required for data transfer, enabling efficient interaction between the CPU and external components.
- **Data Exchange:**
- Within the `CPUBundle`, various signals are defined to support bidirectional data exchange. These signals may include inputs and outputs for addresses, data, control signals, and other relevant information. The well-defined structure of the bundle ensures a standardized and organized approach to communication.
- **Peripheral Devices:**
- The term "peripheral devices" encompasses a broad range of external components, such as memory units, storage devices, and other interfaces. The `CPUBundle` accommodates the specific needs of these peripheral devices, allowing the CPU to read from and write to external memory and interact with other connected devices.
```scala
package riscv
import chisel3._
import peripheral.RAMBundle
// CPUBundle serves as the communication interface for data exchange between
// the CPU and peripheral devices, such as memory.
class CPUBundle extends Bundle {
val instruction_address = Output(UInt(Parameters.AddrWidth))
val instruction = Input(UInt(Parameters.DataWidth))
val instruction_valid = Input(Bool())
val Interrupt_Flag = Input(UInt(Parameters.InterruptFlagWidth))
val memory_bundle = Flipped(new RAMBundle)
val deviceSelect = Output(UInt(Parameters.SlaveDeviceCountBits.W))
val regs_debug_read_address = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val regs_debug_read_data = Output(UInt(Parameters.DataWidth))
val csr_regs_debug_read_address = Input(UInt(Parameters.CSRRegisterAddrWidth))
val csr_regs_debug_read_data = Output(UInt(Parameters.DataWidth))
}
```
# 4. Validation
> Environment: Linux Ubuntu 22.04
>
> Tool: GTKwave
>
The first to test =⇒ Single cycle CPU with CSR
we new folder "Final_Project" including the testing data “"
## 4.1 CPU Validation
### 4.1.1 Testing Step
The
```bash
$ cd Final_Project/
$ sbt test
```
### 4.1.2 Testing Result
- **********Pass**********
```bash
FibonacciTest:
[info] Single Cycle CPU with CSR and CLINT
[info] - should calculate recursively fibonacci(10)
[info] QuicksortTest:
[info] Single Cycle CPU with CSR and CLINT
[info] - should quicksort 10 numbers
[info] InstructionDecoderTest:
[info] InstructionDecoder of Single Cycle CPU
[info] - should produce correct control signal
...
[info] InstructionFetchTest:
[info] InstructionFetch of Single Cycle CPU
[info] - should fetch instruction
[info] ByteAccessTest:
[info] Single Cycle CPU with CSR and CLINT
[info] - should store and load single byte
[info] ExecuteTest:
[info] Execution of Single Cycle CPU
[info] - should execute correctly
[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 46 seconds, 932 milliseconds.
[info] Total number of tests run: 10
[info] Suites: completed 8, aborted 0
[info] Tests: succeeded 9, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
[error] Failed tests:
[error] riscv.singlecycle.SimpleTrapTest
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 60 s, completed Jan 13, 2024, 6:17:58 PM
```
- **********Wrong (Need to find)**********
```bash
[info] SimpleTrapTest:
[info] Single Cycle CPU with CSR and CLINT
[info] - should jump to trap handler and then return *** FAILED ***
[info] java.lang.NullPointerException:
[info] at ... ()
[info] at peripheral.InstructionROM.readAsmBinary(InstructionROM.scala:42)
[info] at peripheral.InstructionROM.<init>(InstructionROM.scala:25)
[info] at riscv.singlecycle.TestTopModule.$anonfun$instruction_rom$2(CPUTest.scala:30)
[info] at chisel3.Module$.do_apply(Module.scala:53)
[info] at riscv.singlecycle.TestTopModule.$anonfun$instruction_rom$1(CPUTest.scala:30)
[info] at chisel3.internal.plugin.package$.autoNameRecursively(package.scala:33)
[info] at riscv.singlecycle.TestTopModule.<init>(CPUTest.scala:30)
[info] at riscv.singlecycle.SimpleTrapTest.$anonfun$new$42(CPUTest.scala:125)
[info] at ... ()
[info] ...
```
## 4.2 GTKWave
- **FibonacciTest (10):**
The expect result is 55

# 5. Reference
1. [RISC-V 指令集架構介紹 - M Standard Extension](https://tclin914.github.io/f37f836/)
2. [中斷和異常 - YatCPU 實驗文件 (sysu.tech)](https://yatcpu.sysu.tech/tutorial/interrupt-and-exception/)
3. [Specifications – RISC-V International (riscv.org)](https://riscv.org/technical/specifications/)
4. [riscv-privileged-20211203.pdf - Google Drive](https://drive.google.com/file/d/1EMip5dZlnypTk7pt4WWUKmtjUKTOkBqh/view)
5. [從零開始的RISC-V SoC架構設計與Linux核心運行 - 硬體篇 - HackMD](https://hackmd.io/@w4K9apQGS8-NFtsnFXutfg/B1Re5uGa5)
6. [RISC V::中斷與異常處理 -- 異常篇 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10268967)
7. [RV32M, RV64M Instructions — riscv-isa-pages documentation (msyksphinz-self.github.io)](https://msyksphinz-self.github.io/riscv-isadoc/html/rvm.html)