Try   HackMD

5-stage pipeline RISC-V core

劉哲維

Task Description

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.

  • 1. Implement forwarding mechanisms to improve data handling in the processor pipeline.
  • 2. Select and rewrite at least 3 RISC-V programs, ensuring they can run on the improved processor.
  • 3. Validate the implementation using simulators such QEMU or rv32emu

Task1: studying the 5-Stage-RV32I project

Outline

  • Environment
  • 5-Stage-RV32I

Environment

Cloning and Using the 5-Stage-RV32I Project
To begin, clone the repository from the official GitHub link and set it up for use:

git clone https://github.com/kinzafatim/5-Stage-RV32I.git

and

cd 5-stage-RV32I

Running the Project with sbt test

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.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

val InstMemory          =   Module(new InstMem ("/home/kinzaa/Desktop/5-Stage-RV32I/src/main/scala/Pipeline/test.txt"))

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:

val InstMemory = Module(new InstMem ("/your/local/path/to/test.txt"))

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

After updating the path, we can run sbt test successfully and see the output
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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.


package Pipeline
import chisel3._
import chisel3.util._

class RegisterFile extends Module {
  val io = IO(new Bundle {
    val rs1       = Input(UInt(5.W))
    val rs2       = Input(UInt(5.W))
    val reg_write = Input(Bool())
    val w_reg     = Input(UInt(5.W))
    val w_data    = Input(SInt(32.W))
    val rdata1    = Output(SInt(32.W))
    val rdata2    = Output(SInt(32.W))
  })
  val regfile = RegInit(VecInit(Seq.fill(32)(0.S(32.W))))

  io.rdata1 := Mux(io.rs1 === 0.U, 0.S, regfile(io.rs1))
  io.rdata2 := Mux(io.rs2 === 0.U, 0.S, regfile(io.rs2))

  when(io.reg_write && io.w_reg =/= 0.U) {
    regfile(io.w_reg) := io.w_data
    printf(p"Register x${io.w_reg} written with value ${io.w_data}\n")
  }
}

Then you can see the output when running sbt test.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

5-Stage-RV32I introduction

Next, the following diagram illustrates the five-stage pipeline structure:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

The process of the five-stage pipeline is as follows:

1. Instruction Fetch (IF)

  • Fetches the instruction from memory and updates the Program Counter (PC).
  • This stage primarily handles instruction address calculation and retrieval.

2. Instruction Decode (ID)

  • Decodes the fetched instruction, parsing the opcode and determining the source of operands (registers or immediate values).
  • Reads data from the register file and generates control signals as needed.

3. Execution (EX)

  • Performs the core logical operation of the instruction, such as arithmetic computations (addition, subtraction, etc.), bitwise operations, or branch target calculations.
  • Handles branch decision-making (if applicable) and prepares the branch target address.

4. Memory Access (MEM)

  • Accesses memory if required by the instruction, such as reading or writing data.
  • Applicable only for Load and Store instructions; other instructions bypass this stage.

5. Write Back (WB)

  • Writes the computation results or memory data back to the register file.
  • Ensures the data is available for subsequent instructions.

Next, i will introduce the project components.

this is the Pipeline structure of this project:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

IF → ID → EX → MEM → WB

IF_ID.scala:

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.

Data Transfer Between IF and ID

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:

package Pipeline

import chisel3._
import chisel3.util._

class IF_ID extends Module {
    val io = IO(new Bundle {
        val pc_in               = Input (SInt(32.W))         // PC in
        val pc4_in              = Input (UInt(32.W))         // PC4 in
        val SelectedPC          = Input (SInt(32.W))
        val SelectedInstr       = Input (UInt(32.W))

        val pc_out              = Output (SInt(32.W))        // PC out
        val pc4_out             = Output (UInt(32.W))        // PC + 4 out
        val SelectedPC_out      = Output (SInt(32.W))
        val SelectedInstr_out   = Output (UInt(32.W))
    })

    val Pc_In               = RegInit (0.S (32.W))
    val Pc4_In              = RegInit (0.U (32.W))
    val S_pc                = RegInit (0.S (32.W))
    val S_instr             = RegInit (0.U (32.W))

    Pc_In                   := io.pc_in
    Pc4_In                  := io.pc4_in
    S_pc                    := io.SelectedPC
    S_instr                 := io.SelectedInstr

    io.pc_out               := Pc_In
    io.pc4_out              := Pc4_In
    io.SelectedPC_out       := S_pc
    io.SelectedInstr_out    := S_instr

    // io.pc_out               := RegNext(io.pc_in)
    // io.pc4_out              := RegNext(io.pc4_in)
    // io.SelectedPC_out       := RegNext(io.SelectedPC)
    // io.SelectedInstr_out    := RegNext(io.SelectedInstr)
}




Input and Output Signals

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.

Data Transfer Between ID and EX

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.

Input and Output Signals

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.

Register Signals

  • Inputs:
    • rs1_in, rs2_in: Source register indices.
    • rd_in: Destination register index.
    • rs1_data_in, rs2_data_in: Data read from source registers.
  • Outputs:
    • rs1_out, rs2_out: Forwarded source register indices.
    • rd_out: Forwarded destination register index.
    • rs1_data_out, rs2_data_out: Forwarded register data.

Immediate and Instruction Signals

  • Inputs:
    • imm: Immediate value from the instruction.
    • func3_in, func7_in: Decoded instruction fields.
  • Outputs:
    • imm_out: Forwarded immediate value.
    • func3_out, func7_out: Forwarded instruction fields.

Control Signals

  • Inputs:
    • 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.
  • Outputs:
    • Forwarded versions of the above control signals, such as ctrl_MemWr_out, ctrl_Reg_W_out, etc.

workflow

  1. Input Stage:

    • The ID stage generates values for rs1_data_in, rs2_data_in, imm, and control signals such as ctrl_AluOp_in and ctrl_Branch_in.
  2. Intermediate Storage:

    • All input signals are registered using RegNext, holding their values for one clock cycle.
  3. Output Stage:

    • Registered values are forwarded as rs1_data_out, imm_out, and ctrl_AluOp_out to the EX stage for execution.

Data Transfer Between EX and MEM

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.

Form:

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.

workflow

  1. Input Stage:

    • The EX stage generates control signals (IDEX_MEMRD, IDEX_MEMWR), ALU results (alu_out), and register data (IDEX_rs2).
  2. Intermediate Storage:

    • Each signal is registered using RegNext, holding its value for one clock cycle.
  3. Output Stage:

    • The MEM stage receives the forwarded signals (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.

Data Transfer Between MEM and WB

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:

workflow:

  1. Input Stage:

    • The MEM stage generates control signals (EXMEM_MEMTOREG, EXMEM_REG_W), data (in_dataMem_out, in_alu_out), and the destination register index (EXMEM_rd).
  2. Intermediate Storage:

    • Each signal is stored in a pipeline register using RegNext, maintaining stability across clock cycles.
  3. Output Stage:

    • The WB stage receives these forwarded signals for performing the final write-back operations.

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.

Hazard unit

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.


Overview of Modules

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.

My Task

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.

Functionality of BranchForward.scala

The primary purpose of this module is to:

  1. Forward Data for Branch Instructions:
    • Ensures that the branch decision logic has access to the required data without waiting for it to propagate through the pipeline.
  2. Reduce Pipeline Stalls:
    • Minimizes control hazards by providing the branch unit with the correct data as early as possible.

Inputs

  • 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.

Outputs

  • forward_rs1, forward_rs2: Control signals indicating the data source for rs1 and rs2 used in branch decision-making.

Key Responsibilities

The BranchForward module addresses four primary types of hazards:

1. ALU Result Hazard

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.

when(io.ID_EX_RD =/= 0.U && io.ID_EX_memRd =/= 1.U) {
  when(io.ID_EX_RD === io.rs1) {
    io.forward_rs1 := "b0001".U
  }
  when(io.ID_EX_RD === io.rs2) {
    io.forward_rs2 := "b0001".U
  }
}

EX/MEM Hazard:

when(io.EX_MEM_RD =/= 0.U && io.EX_MEM_memRd =/= 1.U) {
  when(io.EX_MEM_RD === io.rs1) {
    io.forward_rs1 := "b0010".U
  }
  when(io.EX_MEM_RD === io.rs2) {
    io.forward_rs2 := "b0010".U
  }
}

Summary of Forwarding Signals

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.
  • Pipeline Efficiency: Resolves data hazards for branch instructions without introducing stalls.
  • Scalability: Can be extended to handle additional forwarding scenarios as needed.
  • Simplicity: Encapsulates all forwarding logic in one module for easy debugging and maintenance.

Task1 :Forwarding Module

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.

Key Responsibilities

The Forwarding module detects two types of data hazards and applies forwarding logic to address them:

  1. EX Hazard:
    This occurs when an instruction in the EX stage depends on the result of a previous instruction that has just completed execution.

  2. MEM Hazard:
    This occurs when an instruction in the EX stage depends on a result currently being written back from the MEM/WB stage.

IO Ports and Their Roles

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.

Handling EX Hazard

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:

add x3, x1, x2  
sub x4, x3, x5  

Here, the result of add needs to be forwarded to the sub instruction to prevent a stall.

Detection Logic:

when(io.EXMEM_regWr === "b1".U && io.EXMEM_rd =/= "b00000".U &&
     (io.EXMEM_rd === io.IDEX_rs1) && (io.EXMEM_rd === io.IDEX_rs2)) {
  io.forward_a := "b10".U
  io.forward_b := "b10".U
}

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.

Detection Logic

when(io.MEMWB_regWr === "b1".U && io.MEMWB_rd =/= "b00000".U &&
     (io.MEMWB_rd === io.IDEX_rs1) && (io.MEMWB_rd === io.IDEX_rs2) &&
     ~(io.EXMEM_regWr === "b1".U && io.EXMEM_rd === io.IDEX_rs1)) {
  io.forward_a := "b01".U
  io.forward_b := "b01".U
}

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.

Summary of Forwarding Signals

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.

Expanding the Forwarding Module in the RISC-V 5-Stage Pipeline

Why Expand the Forwarding Module?

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.

Original Forwarding Functionality

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.

Example of EX Hazard:

add x3, x1, x2    
sub x4, x3, x5    

In this case, the result of x3 from the add instruction is forwarded from the EX/MEM stage to the sub instruction.

New Feature: WB Hazard Forwarding

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.

Example of WB Hazard:

lw x3, 0(x1)    // Load value into x3
add x4, x3, x5  // Dependent on x3 from WB stage

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:

// WB Hazard forwarding logic
when(io.WB_regWr === "b1".U && io.WB_rd =/= "b00000".U) {
  when(io.WB_rd === io.IDEX_rs1) {
    io.forward_a := "b11".U  // Forward to rs1
  }
  when(io.WB_rd === io.IDEX_rs2) {
    io.forward_b := "b11".U  // Forward to rs2
  }
}

Forwarding Signal Summary

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.

Implementation Results

Fiest , Add WB Processing Logic at the End of Forwarding.scala

image
Next, Modify the Initialization in Main.scala
image

and
image

Compilation Failed After Running sbt test Following Initialization
image

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:

image

After successfully passing all tests, the forwarding functionality has been successfully expanded and integrated into the project!

image


Verify Test Case1:

2020-Quiz3:Question C

int f(int a, int b) {
    int c = b – a;
     if (c & C01 == 0) /* c is a multiple of 4 */
         return 1;
     int d = f(a – 1, b + 2);
     return 3 * (d + a);
}

Corresponding RISC-V Code

f:    sub a2, a1, a0
      andi a2, a2, __C01__
      bnez a2, ELSE
      li a0, 1
      jr ra 
ELSE: addi sp, sp, -8
      sw a0, 0(sp)
      sw ra, 4(sp)
      addi a0, a0, -1
      addi a1, a1, 2
      jal ra, f
A4:   lw a1, 0(sp)
      lw ra, 4(sp)
L1:   add a0, a0, a1
      slli a1, a0, 1
      add a0, a0, a1
      addi sp, sp, 8
      jr ra

First, create test.S and insert the RISC-V code
Then, run the following three commands

/media/disk2/uuuwei0504/rv32emu/rv32emu/bin/riscv32-unknown-elf-as -o test.o test.S

/media/disk2/uuuwei0504/rv32emu/rv32emu/bin/riscv32-unknown-elf-objcopy -O binary test.o test.bin

xxd -p test.bin > test.txt

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:

image


Task2: Complete and run the perfcounter tests of rv32emu.

Project Objectives:

  • 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.

rv32emu

https://github.com/sysprog21/rv32emu/tree/master

$ git clone https://github.com/sysprog21/rv32emu.git

$ sudo apt install libsdl2-dev libsdl2-mixer-dev

$ make

RISC-V GNU Compiler Toolchain

https://github.com/riscv-collab/riscv-gnu-toolchain.git

Setup

$ git clone https://github.com/riscv/riscv-gnu-toolchain

$ cd cd 5-Stage-RV32I/

$ ./configure --prefix=/media/disk2/uuuwei0504/rv32emu     --with-arch=rv32i_zicsr_zifencei     --with-abi=ilp32cat /media/disk2/uuuwei0504/RISV-v_withchisel/5-Stage-RV32I/riscv-gnu/riscv-gnu-toolchain/build-newlib/riscv32-unknown-elf/newlib/config.log
 1851  touch /media/disk2/uuuwei0504/RISV-v_withchisel/5-Stage-RV32I/test_file

$ make

After downloading the project, there is a Makefile inside. The content is as follows:

.PHONY: clean

include ../../mk/toolchain.mk

CFLAGS = -march=rv32i_zicsr_zifencei -mabi=ilp32 -O2 -Wall

OBJS = \
    getcycles.o \
    getinstret.o \
    sparkle.o \
    main.o
BIN = perfcount.elf

%.o: %.S
	$(CROSS_COMPILE)gcc $(CFLAGS) -c -o $@ $<

%.o: %.c
	$(CROSS_COMPILE)gcc $(CFLAGS) -c -o $@ $<

all: $(BIN)

$(BIN): $(OBJS)
	 $(CROSS_COMPILE)gcc -o $@ $^

clean:
	$(RM) $(BIN) $(OBJS)
    

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:

.PHONY: clean

CROSS_COMPILE = /media/disk2/uuuwei0504/rv32emu/rv32emu/bin/riscv32-unknown-elf-

CFLAGS = -march=rv32i_zicsr_zifencei -mabi=ilp32 -O2 -Wall

OBJS = getcycles.o getinstret.o sparkle.o main.o

BIN = perfcount1.elf

%.o: %.S
        $(CROSS_COMPILE)gcc $(CFLAGS) -c -o $@ $<

%.o: %.c
        $(CROSS_COMPILE)gcc $(CFLAGS) -c -o $@ $<

all: $(BIN)

$(BIN): $(OBJS)
        $(CROSS_COMPILE)gcc -o $@ $^

clean:
        rm -f $(BIN) $(OBJS)

(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:

make

Afterward, the following files will be generated:
getcycles.o, getinstret.o, sparkle.o, main.o, and perfcount1.elf.

image

Testing Environment Issues

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.

rv32emu perfcount1.elf

image