verilog搭建单周期CPU与流水线CPU

目录

  • 实现功能与搭建环境介绍
  • 单周期CPU
    • 整体框图
    • 具体代码
      • 顶层模块
      • 取值
      • 译码
      • 执行
      • 访存
      • 写回
  • 流水线CPU
    • 整体框图
    • 前置知识及思路探讨
      • 如何让流水线流起来~
      • Hazard_detect模块
      • Jump_CU模块

实现功能与搭建环境介绍

本项目基于miniRISC-V指令集,实现其中的18条指令。
工具:vivado2018.3
最终实现单周期CPU频率为25MHz,流水线CPU停留在理论阶段(呃),欢迎探讨:
verilog搭建单周期CPU与流水线CPU_第1张图片

单周期CPU

整体框图

单周期CPU就是说一个时钟周期内完成CPU的所有动作:
verilog搭建单周期CPU与流水线CPU_第2张图片
各模块具体功能:
verilog搭建单周期CPU与流水线CPU_第3张图片

具体代码

顶层模块

module cpu(
    input clk,
    input reset,
    output [31:0] segment
    );
    
    wire clk_lock;
    wire p11_clk;
    
    wire [31:0] inst;               // 指令
    wire [31:0] next_pc;            // 下一条指令位置
    wire [31:0] current_pc;         // 当前指令位置 
    
    wire [1:0] npcOp;               // 下一条指令选择信号
    wire [1:0] WSel;                // 写回内容选择信号
    wire RegWEn;                    // 控制寄存器堆读写
    wire [2:0] ImmSel;              // 立即数选择信号
    wire BSel;                      // B操作数选择信号
    wire BrLt;                      // 分支控制判断信号:是否小于
    wire BrEq;                      // 分支控制判断信号:是否等于    
    wire [3:0] ALUSel;              // ALU选择信号
    wire MemRW;                     // 控制数据存储器的读写
    
    wire [31:0] imm;                // 输入的扩展后的立即数
    wire [31:0] data1;              // rs1存储的内容
    wire [31:0] data2;              // rs2存储的内容
    wire [31:0] dataW;              // 将要存入rd的内容
    wire [31:0] data_b;             // 操作数B
    wire [31:0] data_a;             // 操作数A
    wire [31:0] result;             //ALU计算结果
    wire [31:0] data_from_mem;//来自存储器的数据

    
    // dataW三路选择器:来自DRAM/ALU/PC+4
   assign dataW = (WSel == 2'b00)?(data_from_mem):
                   ((WSel == 2'b01)?(result):
                   ((WSel == 2'b10)?(current_pc+4):2'b00));
                   
   // 选择操作数B:来自data2/imm
   assign data_b = (BSel == 0)?data2:imm;
   assign data_a = data1;
    
    // 分频模块100MHz->25MHz
    cpuclk UCLK (
        .clk_in1 (clk),
        .locked (clk_lock),
        .clk_out1 (p11_clk)
    );
    
   // NPC单元
   npc NPC (
    .clk(p11_clk),
    .reset(reset),               
    .npcOp_i(npcOp),          
    .imm_i(imm),    
    .ra_i(data1),                   
    .next_pc_o(next_pc),
    .current_pc_o(current_pc) 
   ); 
   
   // IM
   prgrom U0_irom (
    .a (current_pc[15:2]),
    .spo (inst)
   );
   
   // RF
   rf RF (
    .clk(p11_clk),
    .reset(reset),
    .RegWEn_i(RegWEn),           
    .rs1_i(inst[19:15]),
    .rs2_i(inst[24:20]),
    .rd_i(inst[11:7]),
    .data_w_i(dataW),
    .data1_o(data1),
    .data2_o(data2)
   );
   
   // IMME
   imme IMME (
    .clk(p11_clk),
    .reset(reset),
    .imm_i(inst[31:7]),
    .ImmSel_i(ImmSel),    
    .imm_o(imm)
   );
   
   // ALU
   alu ALU (
    .clk(p11_clk),
    .reset(reset),
    .data_a_i(data_a),
    .data_b_i(data_b),
    .ALUOp_i(ALUSel),                 
    .BrEq_o(BrEq),               
    .BrLt_o(BrLt),                 
    .data_r_o(result)               
   );
   
  // DRAM   
   data_mem dram (
    .clk(p11_clk),
    .addr_i(result),
    .data_w_i(data2),
    .MemRW(MemRW),
    .data_r_o(data_from_mem),
    
    .segment(segment)
);

   // CU
   cu CU (
    .clk(p11_clk),
    .reset(reset),
    .inst(inst),
    .BrLt_i(BrLt),
    .BrEq_i(BrEq),
    .NPCOp_o(npcOp),
    .RegWEn_o(RegWEn),
    .WSel_o(WSel),
    .ImmSel_o(ImmSel),
    .BSel_o(BSel),
    .ALUSel_o(ALUSel),
    .MemRW_o(MemRW)
   );
 
endmodule

取值

NPC模块:

module npc(
    input clk,
    input reset,                // 复位信号
    input [1:0] npcOp_i,          // npc选择信号
    input [31:0] imm_i,           // 输入的立即数,用于jal,jalr,B型指令的跳转
    input [31:0] ra_i,                   // rs1,用于jalr的跳转
    output wire [31:0] next_pc_o,
    output reg [31:0] current_pc_o
    );
    
    //下一条pc有三种选择:
    //npc = pc+4    'b00
    //npc = pc+imm  'b01
    //npc = rs1+imm 'b10
    
    assign  next_pc_o = (npcOp_i == 2'b00)?(current_pc_o+4):
                       ((npcOp_i == 2'b01)?(current_pc_o+imm_i):
                       ((npcOp_i == 2'b10)?(ra_i+imm_i):32'b0));

    
    always@ (posedge clk) begin
        if (reset == 0) begin
            current_pc_o <= -4;
        end else begin
            current_pc_o <= next_pc_o;
        end
    end
    
endmodule

IM模块:取指单元包含了存放miniRV-1汇编程序的程序ROM (Instructioin ROM, IROM)。我们需要使用Vivado自带的存储IP核Distributed Memory Generator来定义IROM。(也可以自己写)

译码

RF模块:

module rf(
    input clk,
    input reset,
    input RegWEn_i,           //  写使能信号。高电平写,低电平读
    input [4:0] rs1_i,
    input [4:0] rs2_i,
    input [4:0] rd_i,
    input [31:0] data_w_i,
    
    output [31:0] data1_o,
    output [31:0] data2_o
    );
    
    reg [31:0] regFile[0:31];       // 32个寄存器
    integer i;
    
    assign data1_o = regFile[rs1_i];
    assign data2_o = regFile[rs2_i];
    
    always@ (posedge clk) begin
        if (reset == 0) begin
            for (i = 0;i < 32;i = i+1)
                regFile[i] <= 0;
        end else if (RegWEn_i != 0 && rd_i != 0) begin
            regFile[rd_i] <= data_w_i;
        end
    end 
    
endmodule

IMME模块:

module imme(
    input clk,
    input reset,
    input [24:0] imm_i,
    input [2:0] ImmSel_i,    
    output wire [31:0] imm_o
    );
    
    wire [31:0] imm_wire;

    assign imm_wire = (ImmSel_i == 3'b000)?({{11{imm_i[24]}},imm_i[24],imm_i[12:5],imm_i[13],imm_i[23:14],1'b0}):
                       ((ImmSel_i == 3'b001)?({{20{imm_i[24]}},imm_i[24:13]}):
                       ((ImmSel_i == 3'b010)?({{20{imm_i[24]}},imm_i[24:18],imm_i[4:0]}):
                       ((ImmSel_i == 3'b011)?({{12{imm_i[24]}},imm_i[24:5]}):
                       ((ImmSel_i == 3'b100)?({{19{imm_i[24]}},imm_i[24],imm_i[0],imm_i[23:18],imm_i[4:1],1'b0}):32'b0))));

    assign imm_o = imm_wire; 

endmodule

CU模块:

module cu(
    input clk,
    input reset,
    input [31:0] inst,
    input BrLt_i,
    input BrEq_i,
    output reg [1:0] NPCOp_o,
    output reg RegWEn_o,
    output reg [1:0] WSel_o,
    output reg [2:0] ImmSel_o,
    output reg BSel_o,
    output reg [3:0] ALUSel_o,
    output reg MemRW_o
    );
    
    wire [6:0] funct7;
    wire [2:0] funct3;
    wire [6:0] opcode;
    
    assign funct7 = inst[31:25];
    assign funct3 = inst[14:12];
    assign opcode = inst[6:0];
    
    always @(inst or BrEq_i or BrLt_i) begin
        if (reset == 0) begin
            RegWEn_o = 0;
            WSel_o = 0;
            ImmSel_o = 0;
            BSel_o = 0;
            MemRW_o = 0;
            NPCOp_o = 0;
            ALUSel_o = 0;
        end else begin
            case (opcode)
            7'b0110011 : begin          // R型指令
                case(funct3)             // 使用funct3与funct7判断ALUSel
                3'b000:begin
                    if (funct7[5] == 1) begin
                        ALUSel_o = 4'b0001;
                    end else begin
                        ALUSel_o = 4'b0000;
                    end
                end
                3'b111:ALUSel_o = 4'b0010;
                3'b110:ALUSel_o = 4'b0011;
                3'b100:ALUSel_o = 4'b1000;
                3'b001:ALUSel_o = 4'b0100;
                3'b101:begin
                    if (funct7[5] == 0) begin
                        ALUSel_o = 4'b0101;
                    end else begin
                        ALUSel_o = 4'b0110;
                    end
                end
                endcase
                NPCOp_o = 2'b00;
                RegWEn_o = 1;
                WSel_o = 2'b01;
                ImmSel_o = 3'b000;
                BSel_o = 0;
                MemRW_o = 0;
            end
            7'b0010011 : begin          // I型指令
                case(funct3)             // 使用funct3与funct7判断ALUSel
                3'b000:ALUSel_o = 4'b0000;
                3'b111:ALUSel_o = 4'b0010;
                3'b110:ALUSel_o = 4'b0011;
                3'b100:ALUSel_o = 4'b1000;
                3'b001:ALUSel_o = 4'b0100;
                3'b101:begin
                    if (funct7[5] == 0) begin
                        ALUSel_o = 4'b0101;
                    end else begin
                        ALUSel_o = 4'b0110;
                    end
                end
                endcase
                NPCOp_o = 2'b00;
                RegWEn_o = 1;
                WSel_o = 2'b01;
                ImmSel_o = 3'b001;
                BSel_o = 1;
                MemRW_o = 0;
            end
            7'b0000011 : begin          // load指令
                NPCOp_o = 2'b00;
                RegWEn_o = 1;
                WSel_o = 2'b00;
                ImmSel_o = 3'b001;
                BSel_o = 1;
                ALUSel_o = 4'b0000;
                MemRW_o = 0;
            end
            7'b1100111 : begin          // jalr
                NPCOp_o = 2'b10;
                RegWEn_o = 1;
                WSel_o = 2'b10;
                ImmSel_o = 3'b001;
                BSel_o = 0;
                ALUSel_o = 4'b0000;
                MemRW_o = 0;
            end
            7'b0100011 : begin          // S型指令
                NPCOp_o = 2'b00;
                RegWEn_o = 0;
                WSel_o = 2'b00;
                ImmSel_o = 3'b010;
                BSel_o = 1;
                ALUSel_o = 4'b0000;
                MemRW_o = 1;
            end
            7'b1100011 : begin          // B型指令
                case (funct3)
                3'b000:NPCOp_o = (BrEq_i == 1)?2'b01:2'b00;
                3'b001:NPCOp_o = (BrEq_i == 0)?2'b01:2'b00;
                3'b100:NPCOp_o = (BrLt_i == 1)?2'b01:2'b00;
                3'b101:NPCOp_o = (BrLt_i == 0)?2'b01:2'b00;
                endcase
                RegWEn_o = 0;
                WSel_o = 2'b00;
                ImmSel_o = 3'b100;
                BSel_o = 0;
                ALUSel_o = 4'b0001;
                MemRW_o = 0;
            end
            7'b0110111 : begin          // U型指令
                NPCOp_o = 2'b00;
                RegWEn_o = 1;
                WSel_o = 2'b01;
                ImmSel_o = 3'b011;
                BSel_o = 1;
                ALUSel_o = 4'b0111;
                MemRW_o = 0;
            end
            7'b1101111 : begin          // J型指令
                NPCOp_o = 2'b01;
                RegWEn_o = 1;
                WSel_o = 2'b10;
                ImmSel_o = 3'b000;
                BSel_o = 0;
                ALUSel_o = 4'b0000;
                MemRW_o = 0;
            end
            default: NPCOp_o <= 2'b00;
            endcase
        end
    end
    
endmodule

执行

ALU模块:

module alu(
    input clk,
    input reset,
    input [31:0] data_a_i,
    input [31:0] data_b_i,
    input [3:0] ALUOp_i,              // ALU选择信号
    
    output wire BrEq_o,                // 分支判断,判断是否相等
    output wire BrLt_o,                 // 分支判断,判断是否小于
    output wire [31:0] data_r_o               // 计算结果
    );
    
    
    // ADD-000,SUB/B-001,AND-010,OR-011,SLL-100,SRL-101,SRA-110,U型指令,左移12-111,xor-1000
assign data_r_o = (ALUOp_i == 4'b0000)?(data_a_i + data_b_i):
                 ((ALUOp_i == 4'b0001)?(data_a_i + (~data_b_i + 1'b1)):
                 ((ALUOp_i == 4'b0010)?(data_a_i & data_b_i):
                 ((ALUOp_i == 4'b0011)?(data_a_i | data_b_i):
                 ((ALUOp_i == 4'b0100)?(data_a_i << (data_b_i[4:0])):
                 ((ALUOp_i == 4'b0101)?(data_a_i >> (data_b_i[4:0])):
                 ((ALUOp_i == 4'b0110)?($signed($signed(data_a_i) >>> (data_b_i[4:0]))):
                 ((ALUOp_i == 4'b0111)?(data_b_i <<12):
                 ((ALUOp_i == 4'b1000)?(data_a_i^data_b_i):32'b0))))))));
                 
assign BrEq_o = (ALUOp_i == 4'b0001 && data_r_o == 0)?1:0;
assign BrLt_o = (ALUOp_i == 4'b0001 && $signed(data_r_o) < 0)?1:0;

endmodule

访存

DRAM模块:

module data_mem(
    input clk,
    input [31:0] addr_i,
    input [31:0] data_w_i,
    input MemRW,
    output [31:0] data_r_o,
    
    output reg [31:0] segment	// 外设:显示管
    );
    
    wire [31:0] addr;

    assign addr = (addr_i == 32'hFFFFF000)?addr_i:(addr_i - 16'h4000);	// 0xFFFFF000为外设地址。数据存储器与指令存储器采用统一编址,因此数据存储器地址需要减去0x4000
    
     // DRAM   
   dram U_dram (
    .clk    (clk),            // input wire clka
    .a      (addr[15:2]),     // input wire [13:0] addra
    .spo    (data_r_o),        // output wire [31:0] douta
    .we     (MemRW),          // input wire [0:0] wea
    .d      (data_w_i)         // input wire [31:0] dina
);

    always @(posedge clk) begin
        segment <= (addr_i == 32'hFFFFF000 && MemRW)?data_w_i:segment;
    end
    
endmodule

这里访存模块写了一个简陋的总线,连接外设便于上板显示。
其中数据存储器模块dram使用了IP核,同样也使用Distributed Memory Generator来实现。

写回

执行目的寄存器的写入。

流水线CPU

整体框图

流水线整体设计:verilog搭建单周期CPU与流水线CPU_第4张图片
整体框图:
verilog搭建单周期CPU与流水线CPU_第5张图片
verilog搭建单周期CPU与流水线CPU_第6张图片
冲突解决模块具体设计:
verilog搭建单周期CPU与流水线CPU_第7张图片
跳转控制模块具体设计:
verilog搭建单周期CPU与流水线CPU_第8张图片

前置知识及思路探讨

如何让流水线流起来~

啊这个其实我是实现了的,就是指令跳转方面有点问题,不涉及指令跳转倒是正常。
本项目采用五级流水线,一个时钟周期执行一个阶段,所以需要在每级之间加入寄存器保存数据。即有四个流水线寄存器,因为每一级分别运行不同的指令,每条指令在各级之间呈阶梯型传递(所以叫流水线哈哈),所以我们需要考虑每级之间通过寄存器要传递什么数据。比如ID阶段得到的控制信号,在EX阶段与MEM阶段也要使用,而有些数据在此阶段已经使用完毕,所以不用再向下传递。
尝试在理论层面解决数据冲突与指令跳转问题↓

Hazard_detect模块

因为每一级分别运行不同指令,所以指令间难免冲突。
verilog搭建单周期CPU与流水线CPU_第9张图片

数据冒险:
数据冒险是指因无法提供指令执行所需数据而导致指令不能再预期的时钟周期内执行的情况。即之前指令的目的寄存器等于当前指令的源寄存器,ID阶段从源寄存器中所取内容不是期待的内容,所需数据可能停留在之前指令的EX,MEM或WB阶段,还未来得及更新。
需要解决的三种数据冒险:
① R-R1型:期待内容是之前指令ALU的计算结果。
② Load-use型:期待内容是之前指令从DRAM中读出数据。
③ R-R2型:期待内容是之前指令WB阶段的写回数据。
条件判定分别如下:
① R-R1型:EX.RegWEn && EX.rd ≠ 0 && (EX.rd == ID.rs1 || EX.rd == ID.rs2)
② Load-use型:MEM.RegWEn && MEM.rd ≠ 0 && (MEM.rd == ID.rs1 || MEM.rd == ID.rs2)
③ R-R2型:WB.RegWEn && WB.rd ≠ 0 && (WB.rd == ID.rs1 || WB.rd == ID.rs2)
解决方法分别如下:
① R-R1型:前递,运算结果直接作为操作数再次运算。
② R-R2型:前递,要写入寄存器堆中的数据直接作为读出数据。
③ Load-use型:前递+停顿一周期。停顿时需要保存PC,IF-ID寄存器模块中的内容,清除ID-EX寄存器中的内容。

控制冒险:
接收来自Jump_Cu模块的NPCOp信号,若为高电平则代表发生控制冒险,清除IF-ID,ID-EX寄存器中的内容,确保流水线正常运行。

Jump_CU模块

删除单周期流水线中的NPC模块,把涉及jal,jalr,B型指令的npc计算转移至Jump_CU模块。
输入信号Jump[0]区分是否为跳转指令,Jump[1]区分是否为无条件跳转,Jump[2:4]区分为jal,jalr或B型指令中的一种。若输入Jump[0]为0,则当前指令不涉及指令跳转,输出NPCOp为低电平,反之进一步判断;若Jump[1]为1,则为jal,jalr的无条件跳转,输出NPCOp为高电平;反之Jump[1]为0,则为B型指令的有条件跳转,进一步根据ALU模块计算得来的BrLt信号与BrEq信号判定是否需要跳转,若是则输出NPCOp为高电平,反之为低电平。
输出的NPCOp信号交给Hazard_Detect模块,为高电平则代表发生控制冒险,清除IF-ID,ID-EX寄存器中的内容,将计算出的正确npc指令传递给PC模块,运行正确指令。
但是B型指令需要ALU的执行结果判断是否满足条件跳转,jal与jalr可以不用在EX阶段才判断,可以在ID阶段就进行跳转。

你可能感兴趣的:(fpga开发,verilog,经验分享)