跳过正文

RISCV

·7427 字·15 分钟· loading · loading ·
Baoxo
作者
Baoxo
技术宅也会梦见云朵彩虹
作者
hehe
心之所动,且随缘去吧。
目录
hehe

三级流水线RISC—V设计(小试牛刀)
#

何为RISC-V?
#

出身背景:
#

想象一下,在计算机世界的早期,x86和ARM架构就像是两位已经成名已久的老牌明星,它们各自拥有着庞大的粉丝群和广泛的市场影响力。然而,随着时代的发展,这些老牌架构变得越来越复杂,专利和架构授权问题也让许多开发者望而却步。就在这时,一位名叫RISC-V的新人出现了,它带着简洁、开放、灵活的特点,迅速吸引了人们的目光。

设计理念:
#

RISC-V的设计哲学可以用“大道至简”来形容。它摒弃了复杂且不常用的指令,只保留了处理器最常用的指令,通过执行多条常用指令的方式来达到同样的效果。这样一来,处理器的并行度和流水线能力得到了显著提升,功耗也相应降低。同时,RISC-V还采用了模块化设计,用户可以根据自己的需求定制指令集,实现不同的功能和性能优化。

开源特性:
#

相对ARM而言,开源是RISC-V的一大亮点。任何人都可以免费使用、修改和贡献RISC-V的规范和实现,无需支付任何版权费用或许可费用。这种开源特性促进了社区的协作和创新,使得RISC-V能够快速适应新技术和应用需求。同时,RISC-V还拥有一个庞大的生态系统,包括各种硬件设备、软件工具、操作系统、编程语言等,为开发者提供了便捷的开发环境和技术支持。

精简指令集(RISC):
#

RISC-V的指令集非常精简,其中包含最常用的指令,如加减乘除、逻辑运算、比较、存储和加载等。相对X86的复杂指令集,RISC-V的指令集更加简洁,使得处理器的实现更加简单和容易。由于精简的特性,也使得RISC-V更加灵活、更低的功耗、更强的适应性。

设计前的准备:
#


数电相关知识。 verilog基本语法。 vscode/netopad++、vivado/ModelSim。 若有过单片机学习经历,也是"肥肠"好的。

RISC-V知识:
#

一、何为三级“泸水县”:
#

三级流水线是指将指令执行过程分解为取指(IF)、译码(ID)和执行(EX)三个阶段,每个阶段由不同的硬件单元并行处理。这种设计允许多个指令同时在不同阶段进行,从而提高了处理器的吞吐量和效率。

取指阶段(IF):
#

处理器从内存中取出下一条要执行的指令。该阶段的主要任务是读取指令,并将其准备送往下一阶段。

译码阶段(ID):
#

处理器将取到的指令进行译码,将指令分解为各个部分,如操作码、寄存器地址等。该阶段的主要任务是解析指令,确定指令的操作类型和目标寄存器。

执行阶段(EX):
#

处理器根据译码阶段解析出的操作码和操作数,执行指令指定的操作。该阶段的主要任务是完成指令的执行,并将结果存储在寄存器中(DATA BACK).

二、RISC-V指令集:
#

四种核心指令(R/I/S/U):
#

指令格式:
#

core
[core]

格式讲解:
#

opcode 操作码,并且标志指令类型。 rd: 目标寄存器。 funct3和funct7: 用于指定具体的操作(如加法、减法等)。 rs1 和 rs2: 操作数、目标寄存器。 imm: 立即数,用于指定操作数。

指令集类型:
#

R 型指令(寄存器操作):
#
功能:
#

用于寄存器之间的操作,所有操作数都来自寄存器,结果也写入寄存器。

指令格式:
#

Register-Register 指令
[Register-Register 指令

指令讲解:
#

reg 指令
[reg 指令

示例代码:
#
ADD x1, x2, x3      # x1 = x2 + x3
SUB x4, x5, x6      # x4 = x5 - x6
SLT x7, x8, x9      # 如果 x8 < x9(有符号比较),x7 = 1;否则 x7 = 0
SLTU x10, x0, x11   # 如果 x11 != 0,x10 = 1;否则 x10 = 0(相当于 SNEZ x10, x11)
AND x12, x13, x14   # x12 = x13 & x14
SLL x15, x16, x17   # x15 = x16 << (x17 & 0x1F)
SRA x18, x19, x20   # x18 = x19 >>> (x20 & 0x1F)
I 型指令:
#
功能:
#

用于寄存器与立即数之间的操作,其中一个操作数是立即数(直接嵌入指令中)。 PS: 可以初步理解成将常量存入寄存器。

指令格式:
#

Immediate-Register 指令
[Immediate-Register 指令

指令讲解:
#

Immediate 指令
[Immediate 指令

示例代码:
#
ADDI x1, x2, 10     # x1 = x2 + 10
SLTI x3, x4, 20     # 如果 x4 < 20,x3 = 1;否则 x3 = 0
SLTIU x5, x6, 1     # 如果 x6 == 0,x5 = 1;否则 x5 = 0
ANDI x7, x8, 0xFF   # x7 = x8 & 0xFF
XORI x9, x10, -1    # x9 = ~x10(按位取反)
Load 和 store 指令:
#
功能:
#

只有load和store指令可以访问存储器。

指令格式:
#

Load_STORE 指令
[Load_STORE 指令

指令讲解:
#

load_store_open 指令
[load_store_open 指令

常见指令:
#
#LOAD  demo
LW x1, 8(x2)  # 从内存地址 x2 + 8 处读取一个字(32 位)到 x1
LH x3, -4(x4) # 从内存地址 x4 - 4 处读取一个半字(16 位)到 x3
#Store demo
SW x1, 12(x2)  # 将 x1 的值写入内存地址 x2 + 12 处
SB x5, 0(x6)   # 将 x5 的低 8 位写入内存地址 x6 + 0 处
U 型指令(Upper Immediate 指令):
#
功能:
#

用于将 20 位立即数加载到寄存器的高位。

指令格式:
#

Upper Immediate 指令
[Upper Immediate 指令]

指令讲解:
#

Upper_Immediate_open  指令
[Upper_Immediate_open 指令]

示例代码:
#
#用 LUI 构造一个大数:
LUI x1, 0x12345     # x1 = 0x12345000
ADDI x1, x1, 0x678  # x1 = 0x12345000 + 0x678 = 0x12345678
#用 AUIPC 跳转到一个远地址
AUIPC x1, 0x10      # x1 = PC + 0x10000
JALR x0, 0(x1)      # 跳转到 x1 的地址
B 型指令(Branch 指令):
#
功能:
#

用于条件分支,根据寄存器值的比较结果决定是否跳转。

指令格式:
#

Branch-Register 指令
[Branch-Register 指令

指令讲解:
#

BEQ 和 BNE: BEQ:如果 rs1 和 rs2 的值相等,就跳转。 BNE:如果 rs1 和 rs2 的值不相等,就跳转。


BLT 和 BLTU: BLT:如果 rs1 的值小于 rs2 的值(有符号数比较),就跳转。 BLTU:如果 rs1 的值小于 rs2 的值(无符号数比较),就跳转。


BGE 和 BGEU: BGE:如果 rs1 的值大于等于 rs2 的值(有符号数比较),就跳转。 BGEU:如果 rs1 的值大于等于 rs2 的值(无符号数比较),就跳转。

示例代码:
#
#BEQ 和 BNE:
BEQ x1, x2, label  # 如果 x1 == x2,跳转到 label
BNE x3, x4, label  # 如果 x3 != x4,跳转到 label
#BLT 和 BLTU:
BLT x5, x6, label  # 如果 x5 < x6(有符号比较),跳转到 label
BLTU x7, x8, label # 如果 x7 < x8(无符号比较),跳转到 label
#BGE 和 BGEU:
BGE x9, x10, label  # 如果 x9 >= x10(有符号比较),跳转到 label
BGEU x11, x12, label # 如果 x11 >= x12(无符号比较),跳转到 label
J 型指令(Jump 指令):
#
功能:
#

跳转指令

指令格式:
#

jump1 指令
[jump1 指令
jump2 指令
[jump2 指令

指令讲解:
#

jump_open 指令
[jump_open 指令

示例代码:
#
JAL x1, label  # 跳转到 label,并将返回地址(PC + 4)保存到 x1
JAL x0, label  # 跳转到 label,不保存返回地址(相当于伪指令 J label)

JALR x1, x2, 0  # 跳转到 x2 的值对应的地址,并将返回地址(PC + 4)保存到 x1
JALR x0, x3, 8  # 跳转到 x3 + 8 对应的地址,不保存返回地址
NOP指令:
#
功能:
#

占位:在代码中插入 NOP 指令,用于占位或对齐指令流。 延迟:在流水线中插入 NOP 指令,用于解决数据冒险或控制冒险。 调试:在调试时,可以用 NOP 指令替换某些指令,观察程序行为。

指令格式:
#

NOP 指令
[NOP 指令

指令解释:
#

在 RISC-V 中,NOP 指令并不是一个独立的指令,而是通过其他指令实现的。通常,NOP 指令可以表示为 ADDI x0, x0, 0,其含义是将寄存器 x0 的值加 0,结果仍然写入 x0。由于 x0 是零寄存器,其值始终为 0,因此这条指令不会改变任何状态。 NOP 指令的实现方式有很多,包括使用 ADDI x0, x0, 0、NOP、NOP.N 等等。

示例代码:
#
addi x1, x2, 10  # x1 = x2 + 10
nop              # 空操作
addi x3, x4, 20  # x3 = x4 + 20
其他指令:
#

RSCI-V还是存在其他指令的,可以参考RISC-V 指令集

设计实现:
#

设计目的:
#

构建一个简单的RISC-V处理器,实现向目的寄存器写入数读取数据、并进行加减法运算并回写入目的寄存器。

设计框架
#

project
[project

框架模块介绍及设计:
#

1.PC—reg(程序计数器寄存器)
#

作用:
#

保存当前指令的地址,用于跳转指令跳转。

功能:
#

在每个时钟周期更新,指向下一条指令。遇到跳转或分支时,PC会被更新为新的目标地址。

实现:
#
module pc_reg (
    input wire clk,
    input wire rst,
    // input wire [31:0] pc_in,
    output reg [31:0] pc_out
);
    always @(posedge clk or negedge rst) begin
        if(!rst)
            pc_out <= 32'h0;
        else
            pc_out <= pc_out + 32'd4;
    end
    
endmodule
设计细节:
#

1、当复位信号无效(rst为高电平)时,PC会在每个时钟上升沿增加4。 2、增加4是因为RISC-V的指令是32位(4字节)对齐的,因此每条指令占用4字节的地址空间。

2. IF(取址阶段):
#

作用:
#

用于取址,根据PC的值,从ROM中读取指令。

功能:
#

根据PC中的地址,从ROM或指令缓存中读取指令,并将其传递给下一阶段(IF-ID)。

实现:
#
module ifetch (
    input wire [31:0] pc_addr_i,        //from pc_reg
    input wire [31:0] rom_inst_i,       ///from rom
    output wire [31:0] if2rom_addr_o,   //to rom
    output wire [31:0] inst_addr_o,     //to idecode
    output wire [31:0] inst_o           //to idecode
);

    assign if2rom_addr_o = pc_addr_i;   //from rom select directives addr

    assign inst_addr_o = pc_addr_i;     //now system running directives addr

    assign inst_o = rom_inst_i;         //now system running directives (from rom)
    
endmodule
设计细节:
#

1、将PC的值直接作为ROM的访问地址。 2、将ROM中读取的指令直接传递给ID阶段。

3. IF-ID(取址-译码寄存器):
#

作用:
#

用于寄存器文件读写、指令译码、取址、分支判断等。

功能:
#

在流水线中传递指令,确保指令在译码阶段被正确处理。

实现:
#
`include "defines.v"

module if_id (
    input wire clk,
    input wire rst,
    input wire [31:0] inst_i,
    input wire [31:0] inst_addr_i,
    output wire [31:0] inst_o,
    output wire [31:0] inst_addr_o
);

    dff_set #(32) dff1 (clk ,rst, `INST_NOP, inst_i, inst_o);
    dff_set #(32) dff2 (clk ,rst, 32'b0, inst_addr_i, inst_addr_o);

endmodule
设计细节:
#

1、将指令和地址传递给ID-EX阶段。 2、当复位信号无效(rst为高电平)时,指令和地址会一直保持不变。 3、dff_set模块:用于寄存器文件,实现寄存器文件的功能。 4、INST_NOP为空指令,不做任何处理。

4. ID(译码阶段):
#

作用:
#

解析指令并读取寄存器值。

功能:
#

将指令分解为操作码、寄存器编号和立即数等,并从寄存器文件(Regs)中读取操作数。同时,识别指令类型(如算术、分支、加载/存储等)。

实现:
#
`include "defines.v"  // 包含宏定义文件
module idecode (
    //from if_id
    input wire [31:0] inst_i,
    input wire [31:0] inst_addr_i,

    //from regs
    input wire [31:0] rs1_data_i,
    input wire [31:0] rs2_data_i,

    //to regs
    output reg [4:0] rs1_addr_o,
    output reg [4:0] rs2_addr_o,
    
    //to id_ex
    output reg [31:0] inst_o,
    output reg [31:0] inst_addr_o,
    output reg [31:0] op1_o,
    output reg [31:0] op2_o,
    output reg [4:0]  rd_addr_o,
    output reg        reg_wen
);
    
    wire [6:0] opcode ;
    wire [4:0] rd ;
    wire [2:0] funct3 ;
    wire [4:0] rs1 ;
    wire [4:0] rs2 ;
    wire [2:0] funct7 ;
    wire [11:0] imm ;

    assign opcode   = inst_i[6:0];
    assign rd       = inst_i[11:7];
    assign funct3   = inst_i[14:12];
    assign rs1      = inst_i[19:15];
    assign rs2      = inst_i[24:20];
    assign funct7   = inst_i[31:25];
    assign imm      = inst_i[31:20];

    always @(*) begin
        inst_o      = inst_i;
        inst_addr_o = inst_addr_i;
        case (opcode)
            `INST_TYPE_I: begin
                case (funct3)
                    `INST_ADDI: begin
                        rs1_addr_o  = rs1;
                        rs2_addr_o  = 5'b0;
                        op1_o       = rs1_data_i;
                        op2_o       = {{20{imm[11]}},imm};    //符号位扩展
                        rd_addr_o   = rd;
                        reg_wen     =1;
                    end

                    default: begin
                        rs1_addr_o  = 5'b0;
                        rs2_addr_o  = 5'b0;
                        op1_o       = 32'b0;
                        op2_o       = 32'b0;
                        rd_addr_o   = 5'b0;
                        reg_wen     =0;
                    end
                endcase
            end


            `INST_TYPE_R_M: begin
                case (funct3)
                    `INST_ADD_SUB: begin
                        rs1_addr_o  = rs1;
                        rs2_addr_o  = rs2;
                        op1_o       = rs1_data_i;
                        op2_o       = rs2_data_i;    
                        rd_addr_o   = rd;
                        reg_wen     =1;
                    end

                    default: begin
                        rs1_addr_o  = 5'b0;
                        rs2_addr_o  = 5'b0;
                        op1_o       = 32'b0;
                        op2_o       = 32'b0;
                        rd_addr_o   = 5'b0;
                        reg_wen     = 0;
                    end
                endcase
            end
            

            default: begin
                rs1_addr_o  = 5'b0;
                rs2_addr_o  = 5'b0;
                op1_o       = 32'b0;
                op2_o       = 32'b0;
                rd_addr_o   = 5'b0;
                reg_wen     =0;
            end
        endcase
    end

endmodule
设计细节:
#

1、opcode、rd、funct3、rs1、rs2、funct7、imm:这些信号通过位切片从inst_i中提取,分别表示指令的操作码、目标寄存器地址、功能码3、源寄存器1地址、源寄存器2地址、功能码7和立即数字段。 2、根据opcode、funct3等信号,确定指令类型和操作类型。 3、根据指令类型和操作类型,确定目标寄存器地址、源寄存器1地址和源寄存器2地址。 4、取imm的最高位作为符号位,用于符号扩展。 5、控制好reg_wenden信号,控制是否写入目标寄存器。

5. ID-EX(译码-执行寄存器):
#

作用:
#

暂存译码后的指令和操作数,供EX阶段使用。

功能:
#

在流水线中传递译码结果,确保执行阶段能正确获取数据。

实现:
#
`include "defines.v"  // 包含宏定义文件
module id_ex (
    input wire clk,
    input wire rst,
    //frm idecode
    input wire [31:0] inst_i,
    input wire [31:0] inst_addr_i,
    input wire [31:0] op1_i,
    input wire [31:0] op2_i,
    input wire [4:0] rd_addr_i,
    input wire reg_wen_i,
    //to exe
    output wire [31:0]inst_o,
    output wire [31:0]inst_addr_o,
    output wire [31:0]op1_o,
    output wire [31:0]op2_o,
    output wire [4:0]rd_addr_o,
    output wire reg_wen_o
);

    dff_set #(32) dff1 (clk ,rst, `INST_NOP, inst_i, inst_o);
    dff_set #(32) dff2 (clk ,rst, 32'b0, inst_addr_i, inst_addr_o);
    dff_set #(32) dff3 (clk ,rst, 32'b0, op1_i, op1_o);
    dff_set #(32) dff4 (clk ,rst, 32'b0, op2_i, op2_o);
    dff_set #(5) dff5 (clk ,rst, 5'b0, rd_addr_i, rd_addr_o);
    dff_set #(1) dff6 (clk ,rst, 1'b0, reg_wen_i, reg_wen_o);
    
endmodule
设计细节:
#

1、将指令和地址传递给EXE阶段。 2、当复位信号无效(rst为高电平)时,指令和地址会一直保持不变。 3、dff_set模块:用于寄存器文件,实现寄存器文件的功能。

6. EXE(执行阶段):
#

作用:
#

执行指令,计算结果并写入目标寄存器。

功能:
#

根据指令类型进行算术运算、逻辑运算、地址计算等。对于分支指令,计算目标地址并决定是否跳转。

实现:
#
`include "defines.v"  // 包含宏定义文件
module exe (
    //frm idecode
    input wire [31:0] inst_i,
    input wire [31:0] inst_addr_i,
    input wire [31:0] op1_i,
    input wire [31:0] op2_i,
    input wire [4:0] rd_addr_i,
    input wire reg_wen_i,

    //to regs
    output reg [31:0] reg_wdata_o,
    output reg [4:0] reg_waddr_o,
    output reg reg_wen_o

);

    wire [6:0] opcode ;
    wire [4:0] rd ;
    wire [2:0] funct3 ;
    wire [4:0] rs1 ;
    wire [4:0] rs2 ;
    wire [6:0] funct7 ;
    wire [11:0] imm ;

    assign opcode   = inst_i[6:0];
    assign rd       = inst_i[11:7];
    assign funct3   = inst_i[14:12];
    assign rs1      = inst_i[19:15];
    assign rs2      = inst_i[24:20];
    assign funct7   = inst_i[31:25];
    assign imm      = inst_i[31:20];

    always @(*) begin
        case (opcode)
            `INST_TYPE_I: begin
                    case (funct3)
                        `INST_ADDI: begin
                            reg_wdata_o = op1_i + op2_i;
                            reg_waddr_o = rd_addr_i;
                            reg_wen_o   = 1;
                        end
                        default: begin
                            reg_wdata_o = 32'b0;
                            reg_waddr_o = 5'b0;
                            reg_wen_o   = 0;
                        end
                    endcase
                end

            `INST_TYPE_R_M: begin
                case (funct3)
                    `INST_ADD_SUB: begin                        
                        if (funct7==7'b000_0000) begin          //ADD
                            reg_wdata_o = op1_i + op2_i;
                            reg_waddr_o = rd_addr_i;
                            reg_wen_o   = 1;
                        end
                        else if (funct7==7'b010_0000) begin     //SUB
                            reg_wdata_o = op1_i - op2_i;   
                            reg_waddr_o = rd_addr_i;
                            reg_wen_o   = 1;
                        end
                    end
                        
                      default: begin
                        reg_wdata_o = 32'b0;
                        reg_waddr_o = 5'b0;
                        reg_wen_o   = 0;
                    end    
                endcase
            end
        
            default: begin
                reg_wdata_o = 32'b0;
                reg_waddr_o = 5'b0;
                reg_wen_o   = 0;
            end
        endcase
    end
endmodule
设计细节:
#

1、通过 reg_wen_o 信号控制是否需要将结果写回寄存器文件。 2、对于I类型指令,立即数(op2_i)已经在译码阶段完成符号扩展。 3、将计算结果和目标寄存器地址传递给写回阶段,支持流水线操作。 4、其余细节可看ID(译码阶段)。

7. Regs(寄存器文件):
#

作用:
#

存储CPU中的寄存器值。

功能:
#

在ID阶段提供操作数,在WB阶段写回结果。RISC-V通常有32个通用寄存器(x0-x31)。

实现:
#
module regs (
    input wire clk,
    input wire rst,
    //form idecode
    input wire [4:0] reg1_raddr_i,
    input wire [4:0] reg2_raddr_i,
    
    //from ex back
    input wire [4:0] reg_waddr_i,
    input wire [31:0] reg_wdata_i,
    input wire reg_wen_i,

    //to idecode
    output reg [31:0] reg1_rdata_o,
    output reg [31:0] reg2_rdata_o
);
    reg [31:0] regs [31:0];

    //clear regs
    integer  i;
    always @(posedge clk or negedge rst) begin
        if(!rst) begin
            for (i = 0;i<32 ;i=i+1 ) begin
                regs[i] <= 32'h0;
            end
        end
        else if(reg_wen_i&& reg_waddr_i != 5'b0 ) begin //避免写到0号寄存器
            regs[reg_waddr_i] <= reg_wdata_i;
        end
    end

    always @(*) begin
        if(!rst)
            reg1_rdata_o =32'h0;
        else if(reg1_raddr_i== 5'b0)
            reg1_rdata_o = 32'h0;
        else if(reg_wen_i && reg_waddr_i == reg1_raddr_i) //避免读到旧值
            reg1_rdata_o = reg_wdata_i;
        else
            reg1_rdata_o = regs[reg1_raddr_i];
    end

    always @(*) begin
        if(!rst)
            reg2_rdata_o =32'h0;
        else if(reg2_raddr_i== 5'b0)
            reg2_rdata_o = 32'h0;
        else if(reg_wen_i && reg_waddr_i == reg2_raddr_i) //避免读到旧值
            reg2_rdata_o = reg_wdata_i;
        else
            reg2_rdata_o = regs[reg2_raddr_i];
    end
endmodule
设计细节:
#

1、使用 for 循环将所有寄存器初始化为 0。 2、目标寄存器地址 reg_waddr_i 不能为 0(即不能写入零寄存器 x0)。 3、在写回阶段,如果目标寄存器地址与写回地址相同,直接输出写入数据(避免读取旧值)。

读取到旧值的原因:
#

regs_back
[project

当第一个指令回写时,第二个指令的写回结果还没有写入寄存器,此时第二个指令的读操作会读取到第一个指令的写回结果。

8. ROM(只读存储器):
#

作用:
#

存储CPU的指令。

功能:
#

在IF阶段,根据PC地址提供指令。ROM是只读的,指令在运行时不可修改。

实现:
#
module rom (
    input wire [31:0] inst_addr_i,
    output reg [31:0] inst_o
); 
    reg [31:0] rom_mem [0:11]; // 12 insts

    always@(*)begin
        inst_o = rom_mem[inst_addr_i>>2];//pc_reg 4width >>2
    end
    
endmodule
设计细节:
#

RISC-V 架构要求指令是 4 字节对齐 的,即每条指令的地址必须是 4 的倍数。故此处 PC 右移 2 位,即可获得指令的地址。

8.dff_set
#

作用:
#

设置时钟边沿触发的 DFF。

功能:
#

根据输入信号 rst 和 clk,设置时钟边沿触发的 DFF。

实现:
#
module dff_set #(
    parameter DW = 32
)(
    input wire clk,
    input wire rst,
    input wire [DW-1:0] set_data,
    input wire [DW-1:0] data_in,
    output reg [DW-1:0] data_out
);

    always @(posedge clk or negedge rst) begin
        if(!rst)
            data_out <= set_data;
        else 
            data_out <= data_in;
    end
    
endmodule

9.宏定义文件:
#

defines.v
#
// I type inst
`define INST_TYPE_I 7'b0010011
`define INST_ADDI   3'b000
`define INST_SLTI   3'b010
`define INST_SLTIU  3'b011
`define INST_XORI   3'b100
`define INST_ORI    3'b110
`define INST_ANDI   3'b111
`define INST_SLLI   3'b001
`define INST_SRI    3'b101

// L type inst
`define INST_TYPE_L 7'b0000011
`define INST_LB     3'b000
`define INST_LH     3'b001
`define INST_LW     3'b010
`define INST_LBU    3'b100
`define INST_LHU    3'b101

// S type inst
`define INST_TYPE_S 7'b0100011
`define INST_SB     3'b000
`define INST_SH     3'b001
`define INST_SW     3'b010

// R and M type inst
`define INST_TYPE_R_M 7'b0110011
// R type inst
`define INST_ADD_SUB 3'b000
`define INST_SLL    3'b001
`define INST_SLT    3'b010
`define INST_SLTU   3'b011
`define INST_XOR    3'b100
`define INST_SR     3'b101
`define INST_OR     3'b110
`define INST_AND    3'b111
// M type inst
`define INST_MUL    3'b000
`define INST_MULH   3'b001
`define INST_MULHSU 3'b010
`define INST_MULHU  3'b011
`define INST_DIV    3'b100
`define INST_DIVU   3'b101
`define INST_REM    3'b110
`define INST_REMU   3'b111

// J type inst
`define INST_JAL    7'b1101111
`define INST_JALR   7'b1100111

`define INST_LUI    7'b0110111
`define INST_AUIPC  7'b0010111
`define INST_NOP    32'h00000013
`define INST_NOP_OP 7'b0000001
`define INST_MRET   32'h30200073
`define INST_RET    32'h00008067

`define INST_FENCE  7'b0001111
`define INST_ECALL  32'h73
`define INST_EBREAK 32'h00100073

// J type inst
`define INST_TYPE_B 7'b1100011
`define INST_BEQ    3'b000
`define INST_BNE    3'b001
`define INST_BLT    3'b100
`define INST_BGE    3'b101
`define INST_BLTU   3'b110
`define INST_BGEU   3'b111

模块连接:
#

RISC-V-CORE
#

module open_rsicv_top (
    input   wire          clk,          // Input clock signal, 1-bit
    input   wire          rst,          // Input reset signal, 1-bit
    input   wire  [31:0]  inst_i,       // Input instruction signal, 32-bit
    output  wire  [31:0]  inst_addr_o   // Output instruction address signal, 32-bit
);

    // Signals for pc_reg to ifetch
    wire [31:0] pc_out_pc_addr_i;      // PC address signal, 32-bit

    // Signals for ifetch to if_id
    wire [31:0] if_inst_o;             // Instruction signal from ifetch, 32-bit
    wire [31:0] if_inst_addr_o;        // Instruction address signal from ifetch, 32-bit

    // Signals for if_id to idecode
    wire [31:0] if_id_inst_o;          // Instruction signal from if_id, 32-bit
    wire [31:0] if_id_inst_addr_o;     // Instruction address signal from if_id, 32-bit

    // Signals for idecode to regs
    wire [4:0]  id_rs1_addr_o;         // rs1 address signal from idecode, 5-bit
    wire [4:0]  id_rs2_addr_o;         // rs2 address signal from idecode, 5-bit

    // Signals for regs to idecode
    wire [31:0] regs_reg1_rdata_o;     // rs1 data signal from regs, 32-bit
    wire [31:0] regs_reg2_rdata_o;     // rs2 data signal from regs, 32-bit

    // Signals for exe to regs
    wire [4:0]  ex_rd_addr_o;          // Write-back address signal from exe, 5-bit
    wire [31:0] ex_rd_data_o;          // Write-back data signal from exe, 32-bit
    wire        ex_reg_wen_o;          // Write-back enable signal from exe, 1-bit

    // Signals for idecode to id_ex
    wire [31:0] id_inst_o;             // Instruction signal from idecode, 32-bit
    wire [31:0] id_inst_addr_o;        // Instruction address signal from idecode, 32-bit
    wire [31:0] id_op1_o;              // Operand 1 signal from idecode, 32-bit
    wire [31:0] id_op2_o;              // Operand 2 signal from idecode, 32-bit
    wire [4:0]  id_rd_addr_o;          // Destination register address signal from idecode, 5-bit
    wire        id_reg_wen;            // Register write enable signal from idecode, 1-bit

    // Signals for id_ex to exe
    wire [31:0] id_ex_inst_o;          // Instruction signal from id_ex, 32-bit
    wire [31:0] id_ex_inst_addr_o;     // Instruction address signal from id_ex, 32-bit
    wire [31:0] id_ex_op1_o;           // Operand 1 signal from id_ex, 32-bit
    wire [31:0] id_ex_op2_o;           // Operand 2 signal from id_ex, 32-bit
    wire [4:0]  id_ex_rd_addr_o;       // Destination register address signal from id_ex, 5-bit
    wire        id_ex_reg_wen_o;       // Register write enable signal from id_ex, 1-bit

    // Instantiation of pc_reg module
    pc_reg pc_reg_inst (
        .clk(clk),                     // Connect to clock signal
        .rst(rst),                     // Connect to reset signal
        .pc_out(pc_out_pc_addr_i)      // Output PC address signal, 32-bit
    );

    // Instantiation of ifetch module
    ifetch ifetch_inst (
        .pc_addr_i      (pc_out_pc_addr_i),  // Input PC address signal, 32-bit
        .rom_inst_i     (inst_i),            // Input ROM instruction signal, 32-bit
        .if2rom_addr_o  (inst_addr_o),       // Output address signal to ROM, 32-bit
        .inst_addr_o    (if_inst_addr_o),    // Output instruction address signal, 32-bit
        .inst_o         (if_inst_o)          // Output instruction signal, 32-bit
    );

    // Instantiation of if_id module
    if_id if_id_inst (
        .clk         (clk),                 // Connect to clock signal
        .rst         (rst),                 // Connect to reset signal
        .inst_i      (if_inst_o),           // Input instruction signal, 32-bit
        .inst_addr_i (if_inst_addr_o),      // Input instruction address signal, 32-bit
        .inst_o      (if_id_inst_o),        // Output instruction signal, 32-bit
        .inst_addr_o (if_id_inst_addr_o)    // Output instruction address signal, 32-bit
    );

    // Instantiation of idecode module
    idecode idecode_inst (
        // Input signals from if_id module
        .inst_i      (if_id_inst_o),       // Input instruction signal, 32-bit
        .inst_addr_i (if_id_inst_addr_o),  // Input instruction address signal, 32-bit

        // Input signals from regs module
        .rs1_data_i  (regs_reg1_rdata_o),  // Input rs1 data signal, 32-bit
        .rs2_data_i  (regs_reg2_rdata_o),  // Input rs2 data signal, 32-bit

        // Output signals to regs module
        .rs1_addr_o  (id_rs1_addr_o),      // Output rs1 address signal, 5-bit
        .rs2_addr_o  (id_rs2_addr_o),      // Output rs2 address signal, 5-bit

        // Output signals to id_ex module
        .inst_o      (id_inst_o),          // Output instruction signal, 32-bit
        .inst_addr_o (id_inst_addr_o),     // Output instruction address signal, 32-bit
        .op1_o       (id_op1_o),           // Output operand 1 signal, 32-bit
        .op2_o       (id_op2_o),           // Output operand 2 signal, 32-bit
        .rd_addr_o   (id_rd_addr_o),       // Output destination register address signal, 5-bit
        .reg_wen     (id_reg_wen)          // Output register write enable signal, 1-bit
    );

    // Instantiation of regs module
    regs regs_inst (
        // Clock and reset signals
        .clk          (clk),                // Input clock signal, 1-bit
        .rst          (rst),                // Input reset signal, 1-bit

        // Read address signals from idecode module
        .reg1_raddr_i (id_rs1_addr_o),      // Input rs1 read address signal, 5-bit
        .reg2_raddr_i (id_rs2_addr_o),      // Input rs2 read address signal, 5-bit

        // Write-back signals from exe module
        .reg_waddr_i  (ex_rd_addr_o),       // Input write-back address signal, 5-bit
        .reg_wdata_i  (ex_rd_data_o),       // Input write-back data signal, 32-bit
        .reg_wen_i    (ex_reg_wen_o),       // Input write-back enable signal, 1-bit

        // Output read data signals to idecode module
        .reg1_rdata_o (regs_reg1_rdata_o),  // Output rs1 data signal, 32-bit
        .reg2_rdata_o (regs_reg2_rdata_o)   // Output rs2 data signal, 32-bit
    );

    // Instantiation of id_ex module
    id_ex id_ex_inst (
        // Clock and reset signals
        .clk         (clk),                 // Input clock signal, 1-bit
        .rst         (rst),                 // Input reset signal, 1-bit

        // Input signals from idecode module
        .inst_i      (id_inst_o),           // Input instruction signal, 32-bit
        .inst_addr_i (id_inst_addr_o),      // Input instruction address signal, 32-bit
        .op1_i       (id_op1_o),            // Input operand 1 signal, 32-bit
        .op2_i       (id_op2_o),            // Input operand 2 signal, 32-bit
        .rd_addr_i   (id_rd_addr_o),        // Input destination register address signal, 5-bit
        .reg_wen_i   (id_reg_wen),          // Input register write enable signal, 1-bit

        // Output signals to exe module
        .inst_o      (id_ex_inst_o),        // Output instruction signal, 32-bit
        .inst_addr_o (id_ex_inst_addr_o),   // Output instruction address signal, 32-bit
        .op1_o       (id_ex_op1_o),         // Output operand 1 signal, 32-bit
        .op2_o       (id_ex_op2_o),         // Output operand 2 signal, 32-bit
        .rd_addr_o   (id_ex_rd_addr_o),     // Output destination register address signal, 5-bit
        .reg_wen_o   (id_ex_reg_wen_o)      // Output register write enable signal, 1-bit
    );

    // Instantiation of exe module
    exe exe_inst (
        // Input signals from id_ex module
        .inst_i      (id_ex_inst_o),       // Input instruction signal, 32-bit
        .inst_addr_i (id_ex_inst_addr_o),  // Input instruction address signal, 32-bit
        .op1_i       (id_ex_op1_o),        // Input operand 1 signal, 32-bit
        .op2_i       (id_ex_op2_o),        // Input operand 2 signal, 32-bit
        .rd_addr_i   (id_ex_rd_addr_o),    // Input destination register address signal, 5-bit
        .reg_wen_i   (id_ex_reg_wen_o),    // Input register write enable signal, 1-bit

        // Output signals to regs module
        .reg_wdata_o (ex_rd_data_o),       // Output write-back data signal, 32-bit
        .reg_waddr_o (ex_rd_addr_o),       // Output write-back address signal, 5-bit
        .reg_wen_o   (ex_reg_wen_o)        // Output write-back enable signal, 1-bit
    );

endmodule

CORE-ROM-TOP
#

module mini_riscv_soc (
    input wire clk,
    input wire rst,
    output wire led                          // led vivado rtl 必要有输出端口
);
	// open_risc_v to rom 
    wire [31:0] open_risc_v_inst_addr_o;

	//rom to open_risc_v
	wire[31:0] rom_inst_o;
    assign led =1;
    
open_rsicv_top open_rsicv_top_inst (
    .clk        (clk),                      // Connect to clock signal, 1-bit
    .rst        (rst),                      // Connect to reset signal, 1-bit
    .inst_i     (rom_inst_o),               // Connect to instruction signal, 32-bit
    .inst_addr_o(open_risc_v_inst_addr_o)   // Connect to instruction address signal, 32-bit
);

rom rom_inst (
    .inst_addr_i (open_risc_v_inst_addr_o), // Connect to instruction address signal, 32-bit
    .inst_o      (rom_inst_o)               // Connect to instruction output signal, 32-bit
);


endmodule

仿真与仿真文件设计:
#

指令文件设计:
#

文件内容:
#
    00000111100000000000110110010011
    00000101011100000000111000010011
    00000001110011011000111010110011
    01000001110011011000111100110011
指令解读:
#
    000001111000 00000 000 11011 0010011
    opcode  =   0010011             //INST_TYPE_I指令类型 
    rd      =   11011               //x27  
    funct3  =   000                 //INST_ADDI指令
    rs1     =   00000               //x0
    imm     =   000001111000        //imm = 000001111000 --->120
    000001010111 00000 000 11100 0010011
    opcode  =   0010011             //INST_TYPE_I指令类型 
    rd      =   11100               //x28  
    funct3  =   000                 //INST_ADDI指令
    rs1     =   00000               //x0
    imm     =   000001010111        //imm = 000001010111 --->87
    0000000 11100 11011 000 11101 0110011
    opcode  =   0110011             //INST_TYPE_R_M指令类型 
    rd      =   11101               //x29  
    funct3  =   000                 //INST_ADD_SUB指令
    rs1     =   11011               //x27
    rs2     =   11100               //x28
    funct7  =   0000000             //INST_ADD_SUB指令--->ADD
    0100000 11100 11011 000 11110 0110011
    opcode  =   0110011             //INST_TYPE_R_M指令类型 
    rd      =   11110               //x30  
    funct3  =   000                 //INST_ADD_SUB指令
    rs1     =   11011               //x27
    rs2     =   11100               //x28
    funct7  =   0000000             //INST_ADD_SUB指令--->SUB

仿真文件设计:
#

仿真文件内容:
#
module mini_riscv_tb;

	reg clk;
	reg rst;

	always #10 clk = ~clk;
	
	initial begin
		clk <= 1'b1;
		rst <= 1'b0;
		#30;
		rst <= 1'b1;	
	end
	
	//rom 初始化
	initial begin
		$readmemb("D:\\work\\code\\my_riscv\\mini_riscv\\tb\\inst_data_ADD_SUB.txt",mini_riscv_tb.mini_riscv_soc_inst.rom_inst.rom_mem);
	end

	initial begin
		while(1)begin
			@(posedge clk) 
			$display("x27 register value is %d",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[27]);
			$display("x28 register value is %d",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[28]);
			$display("x29 register value is %d,this is ADD test!",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[29]);
			$display("x30 register value is %d,this is SUB test!",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[30]);
			$display("---------------------------");
			$display("---------------------------");
		end
	end
	
	mini_riscv_soc mini_riscv_soc_inst(
		.clk   		(clk),
		.rst 		(rst)
	);
	
endmodule
/*
    PS : D:\\work\\code\\my_riscv\\mini_riscv\\tb\\inst_data_ADD_SUB.txt 要换成自己的绝对路径
    mini_riscv_tb.mini_riscv_soc_inst.rom_inst.rom_mem 要根据自己的例化名字指定到rom的mem中
    下面的display 要换成自己的例化名字
 */

开箱测试:
#

modelsim:
#

此处省略modelsim使用,直接看结果!

结果:
#

modelsim
[modelsimNOP

vivado:
#

此处省略vivado使用,直接看结果!

结果:
#

vivado
[vivado

波形:
#

wave

请看红色框里面代表着数据可以读取写入,并做出正确的运算。

RTL电路:
#

top 模块:
#

rtl_top
[rtl_top

rom 模块:
#

rom
[rom

riscv 模块:
#

riscv
[riscv

pc-reg 模块:
#

pc-reg
[pc-reg

if 模块:
#

ifetch
[ifetch

if_id 模块:
#

if_id
[if_id

id 模块:
#

idcode
[idcode

id_ex 模块:
#

id_ex
[id_ex

exe 模块:
#

exe
[exe

regs 模块:
#

regs
[regs

设计总结:
#

希望大家能够喜欢,并且能够学习到设计cpu的思路,并逐渐提升设计cpu的能力。 若觉得本文对您产生了帮助,请转发给更多人,谢谢! 下面进入感谢环节!

感谢:
#

感谢外瑞罗格UP的教程与开源! 感谢liangkangnan的RISC-V_tiny项目的开源,这对我产生设计rsicv的想法有了极大吸引!

相关文章

STM32-pid平衡车
·2595 字·6 分钟· loading · loading
一个简单的CE修改器教程
·841 字·2 分钟· loading · loading
介绍一下使用七牛云对象存储加速网站
·422 字·1 分钟· loading · loading