劉哲維
My task is studying the 5-Stage-RV32I project.Additionally, I will reference the ChiselRiscV project.
The goal is to complete and run the perfcounter tests of rv32emu.
I will implement forwarding mechanisms and select at least 3 RISC-V programs for modification, ensuring these programs can run on the improved processor.
Cloning and Using the 5-Stage-RV32I Project
To begin, clone the repository from the official GitHub link and set it up for use:
and
Before running the project, you can use the sbt test command to verify its functionality. However, the default path defined by the author in /5-Stage-RV32I/src/main/scala/Pipeline/Main.scala (line 20) might cause the test to fail due to a hardcoded file path.
This will cause an error if the specified path does not exist on my system.
Solution: Updating the File Path
To fix this issue, update the file path in the code to match your local directory. For example:
After updating the path, we can run sbt test successfully and see the output
If you want to observe the changes in registers while running the project with the test file test.hex
, you can modify some parts of the RegisterFile.scala
file.
Then you can see the output when running sbt test
.
Next, the following diagram illustrates the five-stage pipeline structure:
The process of the five-stage pipeline is as follows:
this is the Pipeline structure of this project:
IF → ID → EX → MEM → WB
The IF_ID
module serves as a pipeline register between the Instruction Fetch (IF) and Instruction Decode (ID) stages in a RISC-V processor's pipeline. Its primary purpose is to store and pass critical data (e.g., Program Counter and instructions) from the IF stage to the ID stage while ensuring stable data transfer and mitigating timing issues.
In the pipeline, the Program Counter (pc
) and fetched instruction (SelectedInstr
) are generated during the IF stage. These values are stored in the IF_ID
module's registers and then passed to the ID stage.For instance, the pc
value from the IF stage is handled as follows:
This module consists of four primary signals for input and output operations:
Input Signals:
pc_in
: Receives the Program Counter (PC) value from the IF stage.pc4_in
: Receives the next instruction address (PC + 4
).SelectedPC
: Stores a branch or jump-related Program Counter value.SelectedInstr
: Fetches the current instruction from memory.Output Signals:
pc_out
: Outputs the stored PC value to the ID stage.pc4_out
: Outputs the next instruction address (PC + 4
) to the ID stage.SelectedPC_out
: Outputs the branch or jump-related PC value.SelectedInstr_out
: Outputs the fetched instruction to the ID stage.The ID_EX module bridges the Instruction Decode (ID) and Execution (EX) stages in the pipeline. It stores the control signals, operands, and instruction-related data necessary for execution.
This module contains various input and output signals, organized into the following categories:
IFID_pc4_in
: The address of the next instruction (PC + 4) from the IF/ID stage.IFID_pc4_out
: The forwarded PC + 4 to the EX stage.rs1_in
, rs2_in
: Source register indices.rd_in
: Destination register index.rs1_data_in
, rs2_data_in
: Data read from source registers.rs1_out
, rs2_out
: Forwarded source register indices.rd_out
: Forwarded destination register index.rs1_data_out
, rs2_data_out
: Forwarded register data.imm
: Immediate value from the instruction.func3_in
, func7_in
: Decoded instruction fields.imm_out
: Forwarded immediate value.func3_out
, func7_out
: Forwarded instruction fields.ctrl_MemWr_in
, ctrl_MemRd_in
: Memory write and read enable signals.ctrl_Branch_in
: Branch control signal.ctrl_Reg_W_in
: Register write enable.ctrl_MemToReg_in
: Memory-to-register control signal.ctrl_AluOp_in
, ctrl_OpA_in
, ctrl_OpB_in
: ALU control signals.ctrl_nextpc_in
: Next PC control signal.ctrl_MemWr_out
, ctrl_Reg_W_out
, etc.Input Stage:
rs1_data_in
, rs2_data_in
, imm
, and control signals such as ctrl_AluOp_in
and ctrl_Branch_in
.Intermediate Storage:
RegNext
, holding their values for one clock cycle.Output Stage:
rs1_data_out
, imm_out
, and ctrl_AluOp_out
to the EX stage for execution.The EX_MEM module acts as a pipeline register between the Execution (EX) and Memory Access (MEM) stages. It transfers computed ALU results, control signals, and register data for memory operations or writing back to the register file.
Signal Group | Signal Name | Direction | Description |
---|---|---|---|
Control Signals | IDEX_MEMRD |
Input | Indicates if the instruction requires a memory read. |
IDEX_MEMWR |
Input | Indicates if the instruction requires a memory write. | |
IDEX_MEMTOREG |
Input | Determines if the memory output should be written to a register. | |
IDEX_REG_W |
Input | Specifies if a register write is required. | |
EXMEM_memRd_out |
Output | Forwarded memory read control signal. | |
EXMEM_memWr_out |
Output | Forwarded memory write control signal. | |
EXMEM_memToReg_out |
Output | Forwarded memory-to-register control signal. | |
EXMEM_reg_w_out |
Output | Forwarded register write control signal. | |
ALU and Register Data | alu_out |
Input | Result computed by the ALU. |
IDEX_rs2 |
Input | Data read from the second source register. | |
IDEX_rd |
Input | Index of the destination register. | |
EXMEM_alu_out |
Output | Forwarded ALU computation result. | |
EXMEM_rs2_out |
Output | Forwarded data from the second source register. | |
EXMEM_rd_out |
Output | Forwarded destination register index. |
Input Stage:
IDEX_MEMRD
, IDEX_MEMWR
), ALU results (alu_out
), and register data (IDEX_rs2
).Intermediate Storage:
RegNext
, holding its value for one clock cycle.Output Stage:
EXMEM_memRd_out
, EXMEM_alu_out
) for memory access or further operations.The EX_MEM module is a critical component for ensuring smooth communication between the execution and memory stages in the RISC-V pipeline.
The MEM_WB module serves as the final pipeline register between the Memory Access (MEM) and Write Back (WB) stages. It transfers control signals, memory read data, and ALU computation results to the WB stage for final processing.
This module handles three key types of signals, summarized as follows:
Input Stage:
EXMEM_MEMTOREG
, EXMEM_REG_W
), data (in_dataMem_out
, in_alu_out
), and the destination register index (EXMEM_rd
).Intermediate Storage:
RegNext
, maintaining stability across clock cycles.Output Stage:
The MEM_WB module completes the pipeline's operation, ensuring that results from previous stages are correctly written back to the register file, fulfilling the purpose of the RISC-V instruction cycle.
The Hazard Unit folder contains modules responsible for identifying and resolving pipeline hazards in the RISC-V architecture. These modules ensure efficient execution of instructions by managing data dependencies, structural conflicts, and control flow challenges.
Module Name | Functionality |
---|---|
BranchForward.scala | Handles forwarding logic for branch instructions to reduce stalls. |
Forwarding.scala | Implements general forwarding mechanisms to resolve data dependencies. |
HazardDetection.scala | Detects hazards caused by data dependencies and generates control signals to stall or flush the pipeline. |
StructuralHazard.scala | Resolves hardware resource conflicts, ensuring multiple pipeline stages can operate concurrently. |
In this project, my primary focus is on handling the Forwarding mechanism, which is crucial for resolving Read-After-Write (RAW) hazards and ensuring seamless data flow between pipeline stages.
Before diving into my current task, let me first introduce the completed module BranchForward.scala. This module is designed to handle branch instruction forwarding, a critical component in resolving control hazards in the pipeline.
The primary purpose of this module is to:
ID_EX_RD
, EX_MEM_RD
, MEM_WB_RD
: Destination register indices for instructions in ID/EX, EX/MEM, and MEM/WB stages, respectively.ID_EX_memRd
, EX_MEM_memRd
, MEM_WB_memRd
: Flags indicating whether the instruction in each stage is a memory read operation.rs1
, rs2
: Source registers for the current branch instruction.ctrl_branch
: A control signal specifying whether the current instruction is a branch.forward_rs1
, forward_rs2
: Control signals indicating the data source for rs1
and rs2
used in branch decision-making.The BranchForward module addresses four primary types of hazards:
When a branch instruction depends on the result of an ALU operation in the ID/EX stage, the module forwards the ALU output to the branch logic.
EX/MEM Hazard:
Signal Value | Source | Description |
---|---|---|
0001 |
ID/EX Stage | Forwarding from ALU output. |
0010 |
EX/MEM Stage | Forwarding from memory or ALU result. |
0011 |
MEM/WB Stage | Forwarding from memory or register. |
0110 |
ID/EX Stage (JALR) | Forwarding for unconditional jumps. |
The Forwarding module is essential for resolving data hazards within the RISC-V pipeline. These hazards occur when subsequent instructions require data that is still in the process of being written back. This module ensures that the correct data is forwarded from intermediate pipeline stages, enabling the pipeline to maintain smooth execution without stalling.
The Forwarding module detects two types of data hazards and applies forwarding logic to address them:
EX Hazard:
This occurs when an instruction in the EX stage depends on the result of a previous instruction that has just completed execution.
MEM Hazard:
This occurs when an instruction in the EX stage depends on a result currently being written back from the MEM/WB stage.
Signal | Direction | Width | Description |
---|---|---|---|
IDEX_rs1 |
Input | 5 bits | Source register 1 for the current instruction. |
IDEX_rs2 |
Input | 5 bits | Source register 2 for the current instruction. |
EXMEM_rd |
Input | 5 bits | Destination register of the instruction in the EX/MEM stage. |
EXMEM_regWr |
Input | 1 bit | Write-back enable signal for the EX/MEM stage. |
MEMWB_rd |
Input | 5 bits | Destination register of the instruction in the MEM/WB stage. |
MEMWB_regWr |
Input | 1 bit | Write-back enable signal for the MEM/WB stage. |
forward_a |
Output | 2 bits | Forwarding control signal for rs1 . |
forward_b |
Output | 2 bits | Forwarding control signal for rs2 . |
An EX Hazard occurs when the result from an instruction in the EX stage is needed by the next instruction. The Forwarding module identifies this situation using the following logic:
Example:
Here, the result of add needs to be forwarded to the sub instruction to prevent a stall.
Detection Logic:
io.EXMEM_regWr === "b1".U: Ensures that the EX/MEM stage is writing to a valid register.
io.EXMEM_rd =/= "b00000".U: Excludes writes to x0, which always holds 0 in RISC-V.
io.EXMEM_rd === io.IDEX_rs1 or io.IDEX_rs2: Verifies that the result matches the required source registers.
When the hazard is detected, forward_a and forward_b are set to "b10", indicating that the data should be forwarded from the EX/MEM stage.
io.MEMWB_regWr === "b1".U: Ensures that the MEM/WB stage is writing to a valid register.
~(…): Prevents forwarding from MEM/WB if the data is already being forwarded from EX/MEM.
When a MEM Hazard is detected, forward_a and forward_b are set to "b01", indicating that the data should be forwarded from the MEM/WB stage.
Signal Value | Source | Description |
---|---|---|
00 |
No Forwarding | No hazard detected; default case. |
01 |
MEM/WB Stage | Forward from MEM/WB stage. |
10 |
EX/MEM Stage | Forward from EX/MEM stage. |
In the original 5-stage pipeline implementation, the Forwarding Module only supported forwarding data from the EX/MEM and MEM/WB stages. However, this design lacked support for forwarding from the Write-Back (WB) stage, which can cause stalls in scenarios where data is written back to a register and immediately required by the next instruction.
To address this limitation, I expanded the Forwarding Module to handle WB Hazard.
The original Forwarding Module only handled the following types of hazards:
Forwarding Type | Source Stage | Destination Stage | Description |
---|---|---|---|
EX Hazard | EX/MEM | ID/EX | Forwarding ALU result directly to the next instruction. |
MEM Hazard | MEM/WB | ID/EX | Forwarding memory load result to the dependent instruction. |
In this case, the result of x3 from the add instruction is forwarded from the EX/MEM stage to the sub instruction.
The expanded Forwarding Module now includes WB Hazard handling. This ensures that data written back to the register file can be forwarded immediately to the next instruction if required.
Forwarding Type | Source Stage | Destination Stage | Description |
---|---|---|---|
WB Hazard | MEM/WB (Write-Back) | ID/EX | Forwarding data from the WB stage to prevent pipeline stalls. |
Without WB forwarding, the pipeline would stall while waiting for the value of x3 to be written back to the register file.
implementation in CHISEL:
Signal Value | Source Stage | Description |
---|---|---|
00 |
No Forwarding | No hazard detected, default case. |
01 |
MEM/WB Stage | Forwarding data from MEM/WB stage. |
10 |
EX/MEM Stage | Forwarding data from EX/MEM stage. |
11 |
WB Stage | New: Forwarding data from WB stage. |
Fiest , Add WB Processing Logic at the End of Forwarding.scala
Next, Modify the Initialization in Main.scala
and
Compilation Failed After Running sbt test
Following Initialization
The issue occurred because the WB Data source did not consider the selection between memory and ALU outputs.
To resolve this, I used the Mux
function to ensure that the correct source is selected. Specifically, when MEMWB_memToReg_out
is 1
, the memory data is used; otherwise, the ALU result is selected.
result:
After successfully passing all tests, the forwarding functionality has been successfully expanded and integrated into the project!
Corresponding RISC-V Code
First, create test.S
and insert the RISC-V code
Then, run the following three commands
These three steps involve using the riscv32-unknown-elf-as
tool to convert the RISC-V assembly code (test.S
) into an object file. Then, the riscv32-unknown-elf-objcopy
tool is used to convert the object file (test.o
) into a pure binary format (test.bin
). Finally, the xxd -p
command is used to convert the binary file into a hexadecimal format and save it as test.txt
.
result:
Function Execution Performance:
Test the execution speed and efficiency of sparkle_asm
on the target hardware (or emulator).
Computational Accuracy:
Verify whether sparkle_asm
can correctly process the input state and output the expected results.
Hardware Feature Support:
Test whether the target hardware (or emulator) properly supports the counter functionality.
In this project, I used QEMU and rv32emu to complete the verification.
First, we need to download rv32emu
.
After downloading the project, there is a Makefile
inside. The content is as follows:
First, update the include
to point to the path of toolchain.mk
.
Second, specify the path for CROSS_COMPILE
.
The remaining files do not need any modifications.
after:
(There were numerous issues related to environment setup and path configuration along the way, but everything was successfully resolved and executed in the end.)
After modifying the Makefile
, run the command:
Afterward, the following files will be generated:
getcycles.o
, getinstret.o
, sparkle.o
, main.o
, and perfcount1.elf
.
During testing, I encountered environmental issues when using QEMU, which prevented the successful execution of the project.
However, after switching to rv32emu, the project was successfully executed without any issues.