Skip to content

RISC-V CPU construction

A course project that constructed a pipelined RISC-V CPU based on given Verilog codes.

Wrote, debugged, and modified codes for Arithmetic Logic Unit (ALU), Hazard Unit, and Control Unit with over 20 input/output parameters. Independently coded several modules and ran the entire CPU test to progressively check and optimize each module on the pipeline data path.

See also:https://github.com/AckyZhang/RiscV_zjuclass

Fig 1. the CPU Module Diagram

Example:

Hazard Unit design approach

This design approach aims to handle pipeline conflicts by inserting bubbles, forwarding data, and flushing pipeline stages to address data and control hazards through combinational logic circuits.

Firstly, we consider how to handle Flush and Stall signals, and prioritize them:

1.CpuRst is an external signal used to initialize the CPU. When CpuRst == 1, the CPU is globally reset to zero (flushing all stage registers). This signal has the highest priority.

2.Consider jump and branch instructions. BranchE and JalrE signals are generated in the EX stage. When a jump or branch actually occurs, it’s necessary to flush the EX stage of the next instruction and the ID stage of the instruction after that. The JalD signal is generated in the ID stage, and when a jump occurs, it needs to flush the ID stage of the next instruction. The priority order of these three commands is consistent with what is described in NPC_Generator and will not be reiterated here.

3.Next, consider situations that require a Stall. Since other types of Data Hazards are handled using data forwarding, we only need to insert bubbles in the case where a load instruction immediately follows a reading instruction, which might result in a WAR Hazard. In this case, MemToRegE is set to 1, and there is a conflict between RdE and either Rs1D or Rs2D. This indicates that the first two stage registers need to be stalled and the EX stage should be flushed.

4.Consider the order of the situations described in 3. and 2. If the EX stage is flushed due to a branch or jump according to 2., then no Stall is necessary. Therefore, 3. has lower priority.

5.Handle the default case: when there are no conflicts, neither Flush nor Stall is necessary, and all signals are set to 0.

Next, we address data forwarding. Data forwarding primarily considers whether RegWrite and RegRead are happening (whether registers are being read or written). If a hazard is occurring, it’s determined whether to use the data temporarily stored in the Forward signals based on the register numbers. Refer to the data forwarding logic in RV32Core.v:


assign ForwardData1 = Forward1E[1] ? AluOutM : (Forward1E[0] ? RegWriteData : RegOut1E);
assign ForwardData2 = Forward2E[1] ? AluOutM : (Forward2E[0] ? RegWriteData : R
//Stall and Flush signals generate
always @ (*)
    if(CpuRst)
        {StallF,FlushF,StallD,FlushD,StallE,FlushE,StallM,FlushM,StallW,FlushW} <= 10'b0101010101;
    else if(BranchE | JalrE)
        {StallF,FlushF,StallD,FlushD,StallE,FlushE,StallM,FlushM,StallW,FlushW} <= 10'b0001010000;
    else if(JalD)
        {StallF,FlushF,StallD,FlushD,StallE,FlushE,StallM,FlushM,StallW,FlushW} <= 10'b0001000000;
    else if(MemToRegE & ((RdE==Rs1D)||(RdE==Rs2D)) )
        {StallF,FlushF,StallD,FlushD,StallE,FlushE,StallM,FlushM,StallW,FlushW} <= 10'b1010010000;
    else
        {StallF,FlushF,StallD,FlushD,StallE,FlushE,StallM,FlushM,StallW,FlushW} <= 10'b0000000000;
//Forward1
always@(*)begin
    if( (RegWriteM!=3'b0) && (RegReadE[1]!=0) && (RdM==Rs1E) &&(RdM!=5'b0) )
        Forward1E<=2'b10;//Forward AluOutM
    else if( (RegWriteW!=3'b0) && (RegReadE[1]!=0) && (RdW==Rs1E) &&(RdW!=5'b0) )
        Forward1E<=2'b01;//RegWriteData
    else
        Forward1E<=2'b00;//RegOut
end
//Forward2
always@(*)begin
    if( (RegWriteM!=3'b0) && (RegReadE[0]!=0) && (RdM==Rs2E) &&(RdM!=5'b0) )
        Forward2E<=2'b10;//Forward AluOutM
    else if( (RegWriteW!=3'b0) && (RegReadE[0]!=0) && (RdW==Rs2E) &&(RdW!=5'b0) )
        Forward2E<=2'b01;//RegWriteData
    else
        Forward2E<=2'b00;//RegOut
end

Using testfiles adapted from https://github.com/riscv-software-src/riscv-tests/tree/master/isa/rv32ui (containing 21 instruction testfiles: SLLI、SRLI、SRAI、ADD、SUB、SLL、SLT、SLTU、XOR、SRL、SRA、OR、AND、ADDI、SLTI、SLTIU、XORI、ORI、ANDI、LUI、AUIPC),Preprocess in a Windows environment using self-wrote batch files and generate test files using mingw32-make with the provided makefile and other related toolchain.

Fig 2. Using Vivado and corrosponding testbench to test the CPU