自己动手写CPU_step1_五级流水线设计

Why do?

        为什么要做这么个东西?

        本人正在准备秋招,复习408的计组时,看到冯诺依曼架构(运算器、控制器、存储器和输入输出),突发奇想,学了这么久计算机,自己能不能做一个计算机呢?于是网上搜了一下,使用FPGA就可以实现自制CPU,正好手头有一个正点原子的开发板,于是开启了这个自己动手写CPU的探索之旅。总而言之,兴趣使然。如果你也和我一样,对FPGA感兴趣,对CPU实现感兴趣,欢迎交流讨论。


What do?

        做这么个东西需要做些什么?

        首先CPU分为CISC和RISC架构,CISC的指令很复杂,不利于初学者直接上手,因此,我们从RISC入手,选择MIPS架构实现CPU的设计。RISC架构规定必须要使用流水线设计,分为5个阶段,取指(IF)、译码(ID)、执行(EX)、访存(MEM)、写回(WB),尽管大多数指令不需要访存,因为RISC架构使用了很多通用寄存器,操作数大多保存在寄存器中。整个MIPS架构包含了很多指令,但是,我们只需要先实现一条,然后再去举一反三,一步步完善我们的CPU。在此之前,我们必须先实现一个五级流水线。


How do?

        怎么做?

        实现五级流水主要是明白每一级都在干什么?级间的关系是什么?本人通过仿真测试总结了一下,五级流水分四个时钟周期完成,每一级的实现都是通过组合逻辑实现,不需要时钟控制;而级间的数据传输需要时钟同步,这样既能保证速度也能保证功能。也就是说我们需要设计这些源文件,IF(取指阶段),IF_ID(取值阶段结果送到译码阶段)、ID(译码阶段)、ID_EX(译码阶段的结果送到执行阶段)、EX(执行阶段)、EX_MEM(执行阶段的结果送到访存阶段)、MEM(访存阶段)、MEM_WB(访存阶段的结果写回)。


下面直接给出具体实现,都是一些基本的语法,主要是逻辑。

//取指阶段IF 除复位外,每个时钟都从指令寄存器ROM中获取指令
module pc(
    input                       clk,
    input                       rst,
    output reg [`InstAddrBus]   pc,
    output reg                  pc_en
    );
    
always @ (posedge clk) begin
    if (rst) begin
        pc_en <= 1'd0;      //复位时,指令寄存器禁用
    end else begin
        pc_en <= 1'd1;      
    end
end
    
always @ (posedge clk) begin
    if (pc_en == 1'd0) begin    
        pc <= 32'd0;
    end else begin
        pc <= pc + 32'd4;   //指令寄存器使能时,每个时钟周期取一个指令
    end
end

endmodule
//IF_ID阶段   取出指令后,下一拍发送到译码阶段
module if_id(
    input                       clk,
    input                       rst,
    input  [`InstAddrBus]       if_pc,      //该值由pc_reg模块输出
    input  [`InstDataBus]       if_inst,    //该值由rom指令寄存器输出,只有在pc信号使能时才有效
    output reg [`InstAddrBus]   id_pc,
    output reg [`InstDataBus]   id_inst
    );
    
always @ (posedge clk) begin
    if (rst) begin
        id_pc <= 32'd0;    
        id_inst <= 32'd0;
    end else begin
        id_pc <= if_pc;
        id_inst <= if_inst;
    end
end

endmodule
//译码阶段  对if_id传入的指令进行译码,分离出操作数和操作码
module id(
    input                           rst,
    input [`InstAddrBus]            pc,
    input [`InstDataBus]            inst,
    //读通用寄存器        读取        
    input [`RegDataBus]             reg1_data,
    input [`RegDataBus]             reg2_data,
    output reg                      reg1_rden,
    output reg                      reg2_rden,
    output reg [`RegAddrBus]        reg1_addr,  //源操作数1的地址
    output reg [`RegAddrBus]        reg2_addr,  //源操作数2的地址
    //送到ex阶段的值
    output reg [`RegDataBus]        reg1,       //源操作数1 32b
    output reg [`RegDataBus]        reg2,       //源操作数2 32b
    output reg                      reg_wb,     //写回目的寄存器标志    
    output reg [`RegAddrBus]        reg_wb_addr,//写回目的寄存器地址
    output reg [`AluOpBus]          aluop       //操作码    
    );

/*      ORI指令                               
    31:26   25:21   20:16   15:0
     op      rs      rt      imm
*/

wire [5:0] op = inst[31:26];         //从指令中获取操作码   高6位
reg [`RegDataBus] imm;         //立即数

always @ (*) begin
    if (rst) begin
        reg1_rden       <= 1'd0;
        reg2_rden       <= 1'd0;
        reg1_addr       <= 5'd0;
        reg2_addr       <= 5'd0;
        imm             <= 32'd0;
        reg_wb          <= 1'd0;
        reg_wb_addr     <= 5'd0;
        aluop           <= 6'd0;
    end else begin
        reg1_rden       <= 1'd0;
        reg2_rden       <= 1'd0;
        reg1_addr       <= inst[25:21];   //默认从指令中读取操作数1地址
        reg2_addr       <= inst[20:16];   //默认从指令中读取操作数2地址
        imm             <= 32'd0; 
        reg_wb          <= 1'd0;
        reg_wb_addr     <= inst[15:11];  //默认结果地址寄存器rd        
        aluop           <= inst[31:26];  //高6位为操作码
        //操作类型
        case (op)
            `EXE_ORI:   begin           //或指令  rs寄存器值是操作数1,imm是操作数2,结果放到rt寄存器
                    reg1_rden   <= 1'd1;  //需要读取操作数1 rs寄存器的值
                    reg2_rden   <= 1'd0;  //不需要读取操作数2 rt寄存器值,
                    imm         <= {16'h0, inst[15:0]};   
                    reg_wb      <= 1'd1;
                    reg_wb_addr <= inst[20:16];
            end
        endcase
    end
end

always @ (*) begin
    if (rst) begin
        reg1 <= 32'd0;
    end else if (reg1_rden == 1'd1) begin   //从通用寄存器获取操作数
        reg1 <= reg1_data;
    end else if (reg1_rden == 1'd0) begin   //从指令中获取操作数
        reg1 <= imm;
    end else begin
        reg1 <= 32'd0;
    end
end
    
always @ (*) begin
    if (rst) begin
        reg2 <= 32'd0;
    end else if (reg2_rden == 1'd1) begin   //从通用寄存器获取操作数
        reg2 <= reg2_data;
    end else if (reg2_rden == 1'd0) begin   //从指令中获取操作数
        reg2 <= imm;
    end else begin
        reg2 <= 32'd0;
    end
end

endmodule
//通用寄存器组,用来给出操作数以及写回结果
module id_reg(
    input                       clk,
    input                       rst,
    //读端口       
    input                       reg1_rden,
    input [`RegAddrBus]         reg1_addr,
    output reg [`RegDataBus]    reg1_data,
    input                       reg2_rden,
    input [`RegAddrBus]         reg2_addr,
    output reg [`RegDataBus]    reg2_data,
    //写端口
    input                       wr_en,
    input [`RegAddrBus]         wr_addr,
    input [`RegDataBus]         wr_data
    );
    
reg [`RegDataBus] regs [0:31];  //定义32个32位的寄存器

always @ (*) begin
    if (rst) begin
        reg1_data       <= 32'd0;
    end else if (reg1_addr == 5'd0) begin   //0号寄存器恒为0
        reg1_data       <= 32'd0;
    end else if (reg1_rden) begin
        reg1_data       <= regs[reg1_addr];
    end else begin
        reg1_data       <= 32'd0;
    end
end
    
always @ (*) begin
    if (rst) begin
        reg2_data       <= 32'd0;
     end else if (reg2_addr == 5'd0) begin   //0号寄存器恒为0
        reg2_data       <= 32'd0;
    end else if (reg2_rden) begin
        reg2_data       <= regs[reg2_addr];
    end else begin
        reg2_data       <= 32'd0;
    end
end
    
always @ (posedge clk) begin    //复位时不写,写0寄存器不允许
    if (!rst && (wr_en == 1'd1) && (wr_addr != 5'd0) ) begin
        regs[wr_addr] <= wr_data;
    end
end

endmodule
//将译码阶段的操作数、运算类型、写目的寄存器等信息,下一拍发送到流水线执行阶段
module id_ex(
    input                       clk,
    input                       rst,
    //id阶段信号
    input [`AluOpBus]           id_aluop,
    input [`RegDataBus]         id_reg1,
    input [`RegDataBus]         id_reg2,
    input                       id_reg_wb,
    input [`RegAddrBus]         id_reg_wb_addr,
    //ex阶段信号
    output reg [`AluOpBus]      ex_aluop,
    output reg [`RegDataBus]    ex_reg1,
    output reg [`RegDataBus]    ex_reg2,
    output reg                  ex_reg_wb,
    output reg [`RegAddrBus]    ex_reg_wb_addr
    );
    
    always @(posedge clk) begin
        if (rst) begin
            ex_aluop        <= 6'd0;
            ex_reg1         <= 32'd0;
            ex_reg2         <= 32'd0;
            ex_reg_wb       <= 1'd0;
            ex_reg_wb_addr  <= 5'd0;
        end else begin 
            ex_aluop        <= id_aluop;
            ex_reg1         <= id_reg1;
            ex_reg2         <= id_reg2;
            ex_reg_wb       <= id_reg_wb;
            ex_reg_wb_addr  <= id_reg_wb_addr;
        end
    end
endmodule
//执行阶段,根据译码阶段得到的操作码和操作数进行运算,得到结果
module ex(
    input                       rst,
    input [`AluOpBus]           aluop,
    input [`RegDataBus]         reg1,
    input [`RegDataBus]         reg2,
    input                       reg_wb_i,
    input [`RegAddrBus]         reg_wb_addr_i,
    output reg                  reg_wb_o,
    output reg [`RegAddrBus]    reg_wb_addr_o,
    output reg [`RegDataBus]    reg_wb_data     //写回数据到目的寄存器
    );
    
    always @ (*) begin
        if (rst) begin
            reg_wb_o        <= 1'd0;
            reg_wb_addr_o   <= 5'd0;
            reg_wb_data     <= 32'd0;
        end else begin
            reg_wb_o        <= reg_wb_i;
            reg_wb_addr_o   <= reg_wb_addr_i;
            case (aluop) 
                `EXE_ORI:   begin
                       reg_wb_data      <= reg1 | reg2;
                end
            endcase
        end
    end
endmodule
//将执行阶段的运算结果 在下一拍时发送到流水线的访存阶段
module ex_mem(
    input                       clk,
    input                       rst,
    input [`RegDataBus]         ex_reg_wb_data,
    input                       ex_reg_wb,
    input [`RegAddrBus]         ex_reg_wb_addr,
    output reg [`RegDataBus]    mem_reg_wb_data,
    output reg                  mem_reg_wb,
    output reg [`RegAddrBus]    mem_reg_wb_addr  
    );
    always @ (posedge clk) begin
        if (rst) begin
            mem_reg_wb      <= 1'd0;
            mem_reg_wb_addr <= 5'd0;
            mem_reg_wb_data <= 32'd0;
        end else begin
            mem_reg_wb      <= ex_reg_wb;
            mem_reg_wb_addr <= ex_reg_wb_addr;
            mem_reg_wb_data <= ex_reg_wb_data;
        end
    end
endmodule
//访存阶段  如果不访存就相当于线 传输数据
module mem(
    input                       rst,
    input                       reg_wb_i,
    input [`RegAddrBus]         reg_wb_addr_i,
    input [`RegDataBus]         reg_wb_data_i,
    output reg                  reg_wb_o,
    output reg [`RegAddrBus]    reg_wb_addr_o,
    output reg [`RegDataBus]    reg_wb_data_o
    );
    always @ (*) begin
        if (rst) begin
            reg_wb_o        <= 1'd0;
            reg_wb_addr_o   <= 5'd0;
            reg_wb_data_o   <= 32'd0;
        end else begin
            reg_wb_o        <= reg_wb_i;
            reg_wb_addr_o   <= reg_wb_addr_i;
            reg_wb_data_o   <= reg_wb_data_i;
        end
    end
endmodule
//将访存阶段的结果 下一拍传递到回写阶段 只需要将输出端口连接到目的寄存器端口即可
module mem_wb(
    input                       clk,
    input                       rst,
    input                       mem_reg_wb,
    input [`RegAddrBus]         mem_reg_wb_addr,
    input [`RegDataBus]         mem_reg_wb_data,
    output reg                  wb_reg,
    output reg [`RegAddrBus]    wb_reg_addr,
    output reg [`RegDataBus]    wb_reg_data
    );
    
    always @ (posedge clk) begin
        if (rst) begin
            wb_reg          <= 1'd0;
            wb_reg_addr     <= 5'd0;
            wb_reg_data     <= 32'd0;
        end else begin 
            wb_reg          <= mem_reg_wb;
            wb_reg_addr     <= mem_reg_wb_addr;
            wb_reg_data     <= mem_reg_wb_data;
        end
    end
    
endmodule

        以上代码就是实现五级流水线的全部代码;但是光这些代码还不能实现仿真功能,下一章介绍一个完整的MIPS_SOPC,该设计可以通过仿真测试查看我们设计的流水线是否正常工作。这也是我们一步步设计CPU的关键核心步骤。

        另外,安利一本电子书<<自己动手写CPU>>(该书已绝版,网上找个电子版吧QAQ),本文即根据这本书一步步实现CPU的设计,内容很棒,但是一定要动手!

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值