Try   HackMD

5-stage pipeline RISC-V core

柳嘉祐

study 5-stage-rv32i

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

IF:Fetch the instruction and update the program counter.
ID:Decode the instruction and read the registers.
EX:Execute the operation or compute the address.
MEM:Access memory (such as loading or storing data).
WB:Write the result back to the register.

register

IF_ID reg:Store the data from the IF stage and pass it to the ID stage.
ID_EX reg:Store the data from the ID stage and pass it to the EX stage.
EX_MEM reg:Store the data from the EX stage and pass it to the MEM stage.
MEM_WB reg:Store the data from the MEM stage and pass it to the WB stage.

control unit

// R type instructions (e.g., add, sub)
    is(51.U) {
      io.mem_write := 0.B
      io.branch := 0.B
      io.mem_read := 0.B
      io.reg_write := 1.B
      io.men_to_reg := 0.B
      io.alu_operation := 0.U
      io.operand_A := 0.U
      io.operand_B := 0.B
      io.extend := 0.U
      io.next_pc_sel := 0.U
    }

    // I type instructions (e.g., immediate operations)
    is(19.U) {
      io.mem_write := 0.B
      io.branch := 0.B
      io.mem_read := 0.B
      io.reg_write := 1.B
      io.men_to_reg := 0.B
      io.alu_operation := 1.U
      io.operand_A := 0.U
      io.operand_B := 1.B
      io.extend := 0.U
      io.next_pc_sel := 0.U
    }

Classify instructions based on opcode and funct.

forwarding

add x1, x2, x3  # instruction 1
sub x4, x1, x5  # instruction 2 (depend on x1)
add x1, x2, x3 # instruction 1
add x4, x5, x6 # instruction 2
sub x7, x1, x9 # instruction 3 (depend on x1)

If the add instruction has not yet completed writing back to x1, and the sub instruction already requires the value of x1, a data hazard will occur.
Forwarding allows the sub instruction to directly obtain the result from the ALU output of the add instruction during the EX stage or from MEM stage, instead of waiting for the add instruction to complete the WB stage and then reading from the register.

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

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

If it is certain that the value will be written back to the register, and the rd register is not zero and the MEM rd equals EX rs (1 or 2), the forwarding bit is set to 2'b10.

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 =/= "b00000".U && (io.EXMEM_rd === io.IDEX_rs1) && (io.EXMEM_rd === io.IDEX_rs2))) {
        io.forward_a := "b01".U
        io.forward_b := "b01".U

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

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

If it is certain that the value will be written back to the register, the rd register is not zero, the WB rd equals EX rs (1 or 2), and it is confirmed that there is no forwarding from MEM, the forwarding bit is set to 2'b01.

forwarding bit forwarding from forwarding to
2'b00 non non
2'b10 mem ex
2'b01 wb ex

alu

switch(io.alu_Op) {
    is(ALU_ADD, ALU_ADDI, ALU_SW, ALU_LW, ALU_LUI, ALU_AUIPC) {
      result := io.in_A + io.in_B
    }
    is(ALU_SLL, ALU_SLLI) {
      result := (io.in_A.asUInt << io.in_B(4, 0)).asSInt
    }
    is(ALU_SLT, ALU_SLTI) {
      result := Mux(io.in_A < io.in_B, 1.S, 0.S)
    }
    is(ALU_SLTU, ALU_SLTUI) {
      result := Mux(io.in_A.asUInt < io.in_B.asUInt, 1.S, 0.S)
    }
    is(ALU_XOR, ALU_XORI) {
      result := io.in_A ^ io.in_B
    }
    is(ALU_SRL, ALU_SRLI) {
      result := (io.in_A.asUInt >> io.in_B(4, 0)).asSInt
    }
    is(ALU_OR, ALU_ORI) {
      result := io.in_A | io.in_B
    }
    is(ALU_AND, ALU_ANDI) {
      result := io.in_A & io.in_B
    }
    is(ALU_SUB) {
      result := io.in_A - io.in_B
    }
    is(ALU_SRA, ALU_SRAI) {
      result := (io.in_A >> io.in_B(4, 0)).asSInt
    }
    is(ALU_JAL, ALU_JALR) {
      result := io.in_A
    }
  }

Perform calculations for each category using the ALU based on the classification.

hazard

val Rs1 = io.IF_ID_inst(19, 15)
val Rs2 = io.IF_ID_inst(24, 20)

when(io.ID_EX_memRead === 1.B && ((io.ID_EX_rd === Rs1) || (io.ID_EX_rd === Rs2))) {
    io.inst_forward := true.B
    io.pc_forward := true.B
    io.ctrl_forward := true.B
}.otherwise {
    io.inst_forward := false.B
    io.pc_forward := false.B
    io.ctrl_forward := false.B
}
io.inst_out := io.IF_ID_inst
io.pc_out := io.pc_in
io.current_pc_out := io.current_pc

This code implements Load-Use Hazard detection. When a memory load instruction has not completed its write-back and the next instruction depends on its result, it activates Forwarding or Stalling control signals to ensure data consistency and prevent instruction execution errors.

perfcounter

rv32emu

Build

git clone https://github.com/sysprog21/rv32emu.git
sudo apt install libsdl2-dev libsdl2-mixer-dev

Experimental JIT compilation

sudo apt-get install llvm-18
make ENABLE_JIT=1

result

gcc -march=rv32i_zicsr_zifencei -mabi=ilp32 -O2 -Wall -c -o getcycles.o getcycles.S
gcc: error: unrecognized argument in option ‘-mabi=ilp32’
gcc: note: valid arguments to ‘-mabi=’ are: ms sysv
make: *** [Makefile:15: getcycles.o] Error 1

1.Unsupported Compiler
You might be using the standard GCC for x86 or another platform to compile RISC-V code, but this GCC does not recognize the -march and -mabi options because these options are specific to the RISC-V target.
2.Compiler Prefix Error
The compile command might be using gcc instead of the cross-compiler, such as riscv32-unknown-elf-gcc.

Ask GPT and other solutions on the internet

environment

sudo apt update
sudo apt install gawk
sudo apt install bison flex texinfo

set

cd 5-Stage-RV32I/
git clone https://github.com/riscv/riscv-gnu-toolchain
./configure --prefix=/media/disk2/uuuwei0504/rv32emu     --with-arch=rv32i_zicsr_zifencei     --with-abi=ilp32cat
make

But encounter space-related issues

../.././gcc/gcc/config/riscv/thead-peephole.md:77:1: fatal error: error writing to /tmp/ccD30nlN.s: No space left on device
compilation terminated.
make[2]: *** [Makefile:1198: insn-recog.o] Error 1
make[2]: Leaving directory '/home/liu/5-stage-RV32I/riscv-gnu-toolchain/build-gcc-newlib-stage1/gcc'
make[1]: *** [Makefile:4702: all-gcc] Error 2
make[1]: Leaving directory '/home/liu/5-stage-RV32I/riscv-gnu-toolchain/build-gcc-newlib-stage1'
make: *** [Makefile:651: stamps/build-gcc-newlib-stage1] Error 2

[3] riscv-b

git clone --recurse-submodules https://github.com/riscv/riscv-b.git

build

cd ./riscv-b && make build