CPU设计实战第五章

第四章内容可以参照该篇文章


一、算术逻辑运算类指令的添加

1.ADD、ADDI、SUB指令的添加

不考虑结果溢出,直接复用ADDU、ADDIU、SUBU的数据通路,在译码阶段添加新指令。关于指令的功能定义,读者可以自行翻阅附录C的第3部分。需要注意的是,在这里针对立即数做源操作数时两种不同的扩展(零扩展和符号位扩展)情况新增了两个信号src2_is_imm_zero和src2_is_imm_sign,同时src2_is_imm的位宽变为2,在头文件中也要做相应修改。需要添加和修改的代码如下。
ID_stage.v

wire        inst_add;
wire        inst_addi;
wire        inst_sub;
assign inst_add    = op_d[6'h00] & func_d[6'h20] & sa_d[5'h00];
assign inst_addi   = op_d[6'h08];
assign inst_sub    = op_d[6'h00] & func_d[6'h22] & sa_d[5'h00];
//复用数据通路
assign alu_op[ 0] = inst_addu | inst_addiu | inst_lw | inst_sw | inst_jal | inst_add | inst_addi;
assign alu_op[ 1] = inst_subu | inst_sub;
wire src2_is_imm_zero;//立即数做零扩展的指令
wire src2_is_imm_sign;//立即数做符号位扩展的指令
assign src2_is_imm_zero = inst_lui;
assign src2_is_imm_sign = inst_addi | inst_addiu | inst_lw | inst_sw;
assign src2_is_imm  = src2_is_imm_zero ? 2'b01 : 
                       src2_is_imm_sign ? 2'b10 : 2'b00;
assign dst_is_rt    = inst_addiu | inst_lui | inst_lw | inst_addi;//目的寄存器为rt号寄存器
assign src2_no_rt = inst_addiu | load_op | inst_jal | inst_lui
                     |inst_addi;

mycpu.h

 `define DS_TO_ES_BUS_WD 137

EXE_stage.v

assign es_alu_src2 = es_src2_is_imm == 2'b10 ? {{16{es_imm[15]}}, es_imm[15:0]} : 
                     es_src2_is_imm == 2'b01 ? {16'd0 , es_imm[15:0]}://零扩展
                     es_src2_is_8   ? 32'd8 : es_rt_value;

2.SLTI、SLTIU指令的添加

SLTI指令一方面可以复用SLT指令在执行和访存流水阶段的数据通路,另一方面可以复用ADDIU指令在译码和写回流水阶段的数据通路。相应地,SLTI指令在上述各阶段所需的控制信号也与所复用数据通路对应指令的控制信号一致。
SLTIU指令可以复用SLTU指令在执行和访存流水阶段的数据通路以及ADDIU指令在译码和写回流水阶段的数据通路。SLTIU指令在这些流水阶段所需的控制信号与所复用数据通路所对应指令的控制信号一致。
ID_stage.v

wire        inst_slti;
wire        inst_sltiu;
assign inst_slti   = op_d[6'h0a];
assign inst_sltiu  = op_d[6'h0b];
assign alu_op[ 2] = inst_slt | inst_slti;
assign alu_op[ 3] = inst_sltu | inst_sltiu;
assign src2_is_imm_sign = inst_addi | inst_addiu | inst_lw | inst_sw | 
                          inst_slti | inst_sltiu;
assign dst_is_rt    = inst_addiu | inst_lui | inst_lw | inst_addi 
                      | inst_slti | inst_sltiu;                   
assign src2_no_rt = inst_addiu | load_op | inst_jal | inst_lui
                     |inst_addi |  inst_slti | inst_sltiu;

3.ANDI、ORI、XORI指令的添加

三条指令与分别与AND、OR、XOR指令的逻辑运算功能是一样的,ANDI、ORI和XORI均是将结果写入第rt号寄存器中,这与ADDIU指令是一样的。因此,ANDI、ORI和XORI指令可以复用AND、OR 和XOR指令在执行和访存阶段的数据通路以及ADDIU指令在写回阶段的数据通路。同时它们的第二个源操作数均做零扩展。
ID_stage.v

wire        inst_andi;
wire        inst_ori;
wire        inst_xori;
assign inst_andi   = op_d[6'h0c];
assign inst_ori    = op_d[6'h0d];
assign inst_xori   = op_d[6'h0e];
assign alu_op[ 4] = inst_and | inst_andi;
assign alu_op[ 6] = inst_or | inst_ori;
assign alu_op[ 7] = inst_xor | inst_xori;
assign src2_is_imm_zero = inst_andi | inst_ori | inst_xori | inst_lui;
assign dst_is_rt    = inst_addiu | inst_lui | inst_lw | inst_addi | 
                      inst_slti| inst_sltiu | inst_andi | inst_ori | 
                      inst_xori;
assign src2_no_rt = inst_addiu | load_op | inst_jal | inst_lui
                     |inst_addi | inst_slti | inst_sltiu | inst_andi
                     | inst_ori | inst_xori;

4.SLLV、SRLV、SRAV指令的添加

SLLV、SRLV、SRAV指令在执行、访存、写回阶段的数据通路可以分别复用SLL、SRL和SRA 指令的数据通路,而它们在译码阶段的数据通路可以复用ADDU指令的数据通路。
ID_stage.v

wire        inst_sllv;
wire        inst_srav;
wire        inst_srlv;
assign inst_sllv   = op_d[6'h00] & func_d[6'h04] & sa_d[5'h00];
assign inst_srlv   = op_d[6'h00] & func_d[6'h06] & sa_d[5'h00];
assign inst_srav   = op_d[6'h00] & func_d[6'h07] & sa_d[5'h00];
assign alu_op[ 8] = inst_sll | inst_sllv;
assign alu_op[ 9] = inst_srl | inst_srlv;
assign alu_op[10] = inst_sra | inst_srav;

二、乘除法运算类指令的添加

关于乘除法运算的部件书中介绍了两种设计方法,可以参照书中调用Xilinx IP的方法,这里采用电路级的方法实现。

1.迭代除法器

根据迭代除法器的原理,给出除法器模块的代码。
div.v

`timescale 1ns / 1ps
`include "mycpu.h"
module div(
 input wire clk,
    input wire rst,
    input wire signed_div_i,//是否为有符号除法       
    input wire[31:0] opdata1_i,//被除数
    input wire[31:0] opdata2_i,//除数    
    input wire start_i,//是否开始除法运算       
    input wire annul_i,//是否取消除法运算 
    //input wire ws_ex,
   // input wire ws_eret,   
    output reg[63:0] result_o,//除法运算结果 
    output reg ready_o       //除法运算是否结束      
    );
    wire[32:0] div_temp;
    reg[5:0] cnt;//记录了试商法进行了几轮                   
    reg[64:0] dividend;//最低位保存每次迭代的结果,高32位保存每次迭代时的被减数
    reg[1:0] state;
    reg[31:0] divisor;//除数
    reg[31:0] temp_op1;
    reg[31:0] temp_op2;
    assign div_temp = {1'b0, dividend[63:32]} - {1'b0, divisor};
    always @ (*) begin
        if(signed_div_i == 1'b1 && opdata1_i[31] == 1'b1) begin//有符号除法,并且操作数为负数
            temp_op1 <= ~opdata1_i + 1;//取补码
        end else begin
            temp_op1 <= opdata1_i;
        end
        if(signed_div_i == 1'b1 && opdata2_i[31] == 1'b1) begin 
            temp_op2 <= ~opdata2_i + 1;
        end else begin
            temp_op2 <= opdata2_i;
        end
    end
    always @ (posedge clk) begin
        if (rst == 1'b0) begin
            state <= 2'b00;
            ready_o <= 1'b0;
            result_o <= {32'h00000000, 32'h00000000};
        end begin 
            case (state)
                2'b00: begin//DivFree状态
                        if(start_i == 1'b1 && annul_i == 1'b0) begin
                            if(opdata2_i == 32'h00000000) begin
                                state <= 2'b01; 
                            end else begin
                                state <= 2'b10;    //进入DivOn状态   
                                cnt <= 6'b000000;
                                dividend <= {32'h00000000, 32'h00000000};
                                dividend[32:1] <= temp_op1;
                                divisor <= temp_op2;
                            end
                        end else begin//没有开始除法运算
                            ready_o <= 1'b0;
                            result_o <= {32'h00000000, 32'h00000000};
                        end
                    end
                    2'b01: begin//DivByZero状态
                        dividend <= {32'h00000000, 32'h00000000};
                        state <= 2'b11;
                    end
                    2'b10: begin//DivOn状态
                        if (annul_i == 1'b0) begin
                            if(cnt != 6'b100000) begin
                                if(div_temp[32] == 1'b1) begin//减法结果为负数
                                    dividend <= {dividend[63:0], 1'b0};
                                end else begin
                                    dividend <= {div_temp[31:0], dividend[31:0], 1'b1};
                                end
                                cnt <= cnt + 1;
                            end else begin
                                if((signed_div_i == 1'b1) 
                                    && ((opdata1_i[31] ^ opdata2_i[31]) == 1'b1)) begin 
                                    dividend[31:0] <= (~dividend[31:0] + 1);
                                end
                                if((signed_div_i == 1'b1)
                                    && ((opdata1_i[31] ^ dividend[64]) == 1'b1)) begin
                                    dividend[64:33] <= (~dividend[64:33] + 1);
                                end
                                state <= 2'b11;
                                cnt <= 6'b000000;
                            end
                        end else begin
                            state <= 2'b00;
                        end
                    end
                    2'b11: begin//DivEnd状态
                        result_o<= {dividend[64:33], dividend[31:0]};
                        ready_o <= 1'b1;
                        if (start_i == 1'b0) begin
                            state <= 2'b00;
                            ready_o <= 1'b0;
                            result_o <= {32'h00000000, 32'h00000000};
                        end
                    end
                endcase
            end
        end
endmodule

2.乘法器

mult.v

`timescale 1ns / 1ps
`include "mycpu.h"
module mult(
    input wire clk,
    input wire reset,
    input wire signed_mult_i,       
    input wire[31:0] opdata1_mult,//被乘数
    input wire[31:0] opdata2_mult,//乘数    
    output reg[63:0] mult_result //结果
    );         
    reg[31:0] mult_op1;
    reg[31:0] mult_op2;
    wire [63:0] mult_temp;
    always @ (*) begin
        if(signed_mult_i == 1'b1 && opdata1_mult[31] == 1'b1) begin
           mult_op1 <= ~opdata1_mult + 1;
        end else begin
            mult_op1 <= opdata1_mult ;
        end
        if(signed_mult_i == 1'b1 && opdata2_mult[31] == 1'b1) begin 
            mult_op2 <= ~opdata2_mult + 1;
        end else begin
            mult_op2 <= opdata2_mult;
        end
    end
    assign mult_temp = mult_op1 * mult_op2;
    always @(*) begin
        if(reset) begin
            mult_result <= 64'd0;
    end
    else if(signed_mult_i == 1'b1) begin
        if(opdata1_mult[31] ^ opdata2_mult[31] == 1'b1) begin
            mult_result <= ~mult_temp + 1;
        end
        else begin
            mult_result <= mult_temp;
        end
    end
    else begin
        mult_result <= mult_temp;
    end
end
endmodule

3.特殊寄存器HILO的添加

由于32位乘法运算结果的高32位需写入HI寄存器,低32位需写入LO寄存器,除法运算将商保存在LO寄存器,余数保存在HI寄存器,故增加HILO模块。
hilo.v

`timescale 1ns / 1ps
`include "mycpu.h"
module hilo_reg(
    input wire clk,
    input wire reset,
    input wire [31:0] hi_i,//要写入HI的值
    input wire [31:0] lo_i,//要写入LO的值
    output reg [31:0] lo_o,//LO寄存器的值
    output reg [31:0] hi_o //HI寄存器的值
    );  
always@(*)begin
    if(reset)begin
        hi_o<=32'h0;
        lo_o<=32'h0;
    end else begin
        hi_o<=hi_i;
        lo_o<=lo_i;
    end
end
endmodule

4.例化

在译码阶段添加乘除法相关指令,同时添加mult_div信号,用于标志无符号除法、有符号除法、无符号乘法、有符号乘法,并通过译码与执行阶段的流水线缓存传递给执行阶段,所以在头文件中需要修改相应位宽。
ID_stage.v

wire        inst_mult;
wire        inst_multu;
wire        inst_div;
wire        inst_divu;
wire [3:0] mult_div;
assign inst_mult   = op_d[6'h00] & func_d[6'h18] & rd_d[5'h00] & sa_d[5'h00];
assign inst_multu  = op_d[6'h00] & func_d[6'h19] & rd_d[5'h00] & sa_d[5'h00];
assign inst_div    = op_d[6'h00] & func_d[6'h1a] & rd_d[5'h00] & sa_d[5'h00];
assign inst_divu   = op_d[6'h00] & func_d[6'h1b] & rd_d[5'h00] & sa_d[5'h00];
assign mult_div = {inst_divu,inst_div,inst_multu,inst_mult};
ssign ds_to_es_bus = {
                       mult_div    ,   //140:137
                       alu_op      ,  //136:125
                       load_op     ,  //124:124
                       src1_is_sa  ,  //123:123
                       src1_is_pc  ,  //122:122
                       src2_is_imm ,  //121:120
                       src2_is_8   ,  //119:119
                       gr_we       ,  //118:118
                       mem_we      ,  //117:117
                       dest        ,  //116:112
                       imm         ,  //111:96
                       rs_value    ,  //95 :64
                       rt_value    ,  //63 :32
                       ds_pc          //31 :0
                      };
assign gr_we        = ~inst_sw & ~inst_beq & ~inst_bne & ~inst_jr //不需要写回寄存器的指令
                     & ~inst_mult & ~inst_multu & ~inst_div & ~inst_divu;

执行阶段例化调用乘法、除法以及hilo模块,并根据译码阶段传递来的信号进一步判断乘除法运算的具体类型及运算结果写入HILO寄存器的方式。这里需要特别注意,迭代除法器需要多个时钟周期才能得到运算结果,故执行阶段的ready_go信号的状态需要根据除法器的运算状态进行修改。
EXE_stage.v

wire [3:0] mult_div;
assign {
        mult_div       ,   //140:137
        es_alu_op      ,  //136:125
        es_load_op     ,  //124:124
        es_src1_is_sa  ,  //123:123
        es_src1_is_pc  ,  //122:122
        es_src2_is_imm ,  //121:120
        es_src2_is_8   ,  //119:119
        es_gr_we       ,  //118:118
        es_mem_we      ,  //117:117
        es_dest        ,  //116:112
        es_imm         ,  //111:96
        es_rs_value    ,  //95 :64
        es_rt_value    ,  //63 :32
        es_pc             //31 :0
       } = ds_to_es_bus_r;
assign es_ready_go    =  es_valid && (mult_div[3:2] == 2'b00| ((mult_div[3:2] != 2'b00)&&(div_ready_i== 1'b1)));
//除法部件
wire [63:0] div_result_i;//除法运算结果
wire div_ready_i;//除法运算是否结束
wire div_start_o;//除法运算是否开始
wire signed_div_o;//是否为有符号除法
assign div_start_o  = ((mult_div[3:2] != 2'b00) && div_ready_i == 1'b0) ? 1'b1 : 1'b0;
assign signed_div_o = (mult_div[2] == 1'b1) ? 1'b1 :
                      (mult_div[3] == 1'b1) ? 1'b0 : 1'b0;
div div0 (
  .clk(clk),                    
  .rst(~reset),    
  .signed_div_i(signed_div_o), 
  .opdata1_i(es_alu_src1),  
  .opdata2_i(es_alu_src2),  
  .start_i(div_start_o),
  .annul_i(1'b0),
  .result_o(div_result_i),   
  .ready_o(div_ready_i) 
);

//乘法部件
wire [63:0] mult_result_i;
wire signed_mult_o;
assign signed_mult_o=(mult_div[0]==1'b1)?1'b1:
                     (mult_div[1]==1'b1)?1'b0:1'b0;
mult mult0(
    .clk(clk),
    .reset(reset),
    .signed_mult_i(signed_mult_o),       
    .opdata1_mult(es_alu_src1),
    .opdata2_mult(es_alu_src2),  
    .mult_result(mult_result_i) 
);

//特殊寄存器HI LO
reg  [31:0] lo_i;//要写入lo寄存器的值
reg  [31:0] hi_i;
wire [31:0] lo;//Lo寄存器的值
wire [31:0] hi;
hilo_reg hilo_reg0(
    .clk(clk),
    .reset(reset),
    .hi_i(hi_i),
    .lo_i(lo_i),
    .lo_o(lo),
    .hi_o(hi)
    );
always @ (posedge clk) begin
  if(mult_div != 4'b0000)begin
        if(mult_div[1:0] != 2'b00) 
            lo_i <= mult_result_i[31: 0];
        else if(mult_div[3:2] != 2'b00) 
            lo_i<= div_result_i[31: 0];
   end
end
always @ (posedge clk) begin
   if(mult_div != 4'b0000)begin
     if(mult_div[1:0] != 2'b00)
       hi_i <= mult_result_i[63:32];
     else if(mult_div[3:2] != 2'b00)
        hi_i <= div_result_i[63:32];
    end
end

5.乘除法配套搬运指令的添加

由于乘除法指令的结果都存放在HI、LO寄存器中,其他运算、转移、访存指令只能访问通用寄存器,因此需要指令在HI、LO 寄存器和通用寄存器之间互相传递数据。MIPS指令系统规范一共定义了四条这样的指令:MFHI读取HI寄存器的值,并写入通用寄存器; MFLO读取LO寄存器的值,并写入通用寄存器; MTHI将通用寄存器的值写入HI寄存器; MTLO将通用寄存器的值写入LO寄存器。
在译码阶段添加指令,同时添加一个mf_mt信号,通过译码与执行级之间的流水线缓存传递给执行阶段,用于判断搬运指令的具体类型,从而决定写通用寄存器,还是写特殊寄存器HILO寄存器。对于MFHI 和 MFLO指令来说,它们没有来自寄存器的源操作数,故对gr_we做相应修改。
ID_stage.v

wire        inst_mfhi;
wire        inst_mflo;
wire        inst_mthi;
wire        inst_mtlo;
wire [3:0] mf_mt;
assign inst_mthi   = op_d[6'h00] & func_d[6'h11] & rt_d[5'h00] & rd_d[5'h00] & sa_d[5'h00];
assign inst_mtlo   = op_d[6'h00] & func_d[6'h13] & rt_d[5'h00] & rd_d[5'h00] & sa_d[5'h00];
assign inst_mfhi   = op_d[6'h00] & func_d[6'h10] & rt_d[5'h00] & rs_d[5'h00] & sa_d[5'h00];
assign inst_mflo   = op_d[6'h00] & func_d[6'h12] & rt_d[5'h00] & rs_d[5'h00] & sa_d[5'h00];
assign mf_mt = {inst_mthi,inst_mtlo,inst_mfhi,inst_mflo};
assign ds_to_es_bus = {
                       mf_mt       ,  //144:141
                       mult_div    ,   //140:137
                       alu_op      ,  //136:125
                       load_op     ,  //124:124
                       src1_is_sa  ,  //123:123
                       src1_is_pc  ,  //122:122
                       src2_is_imm ,  //121:120
                       src2_is_8   ,  //119:119
                       gr_we       ,  //118:118
                       mem_we      ,  //117:117
                       dest        ,  //116:112
                       imm         ,  //111:96
                       rs_value    ,  //95 :64
                       rt_value    ,  //63 :32
                       ds_pc          //31 :0
                      };
assign gr_we    = ~inst_sw & ~inst_beq & ~inst_bne & ~inst_jr & ~inst_j
                  & ~inst_mult & ~inst_multu & ~inst_div & ~inst_divu
                  & ~inst_mthi & ~inst_mtlo;

执行阶段根据mf_mt信号的值判断搬运指令的类型,若为MTHI、MTLO指令,则在写HILO部分增加对应的数据通路。若为MFHI、MFLO指令,需将HI、LO寄存器的值传递给回写阶段,这里需要增加一个“三选一”部件,用于选择写回寄存器的值是执行阶段的算术逻辑运算结果还是高位或低位寄存器的值。
EXE_stage.v

wire [3:0] mf_mt;
wire [31:0] es_result;
assign {
        mf_mt          ,  //144:141
        mult_div       ,   //140:137
        es_alu_op      ,  //136:125
        es_load_op     ,  //124:124
        es_src1_is_sa  ,  //123:123
        es_src1_is_pc  ,  //122:122
        es_src2_is_imm ,  //121:120
        es_src2_is_8   ,  //119:119
        es_gr_we       ,  //118:118
        es_mem_we      ,  //117:117
        es_dest        ,  //116:112
        es_imm         ,  //111:96
        es_rs_value    ,  //95 :64
        es_rt_value    ,  //63 :32
        es_pc             //31 :0
       } = ds_to_es_bus_r;
assign es_to_ms_bus = {es_res_from_mem,  //70:70
                       es_gr_we       ,  //69:69
                       es_dest        ,  //68:64
                       es_result      ,  //63:32 修改
                       es_pc             //31:0
                      };
assign es_result = mf_mt[0] == 1'b1 ? lo ://特殊寄存器HILO写通用寄存器
                   mf_mt[1] == 1'b1 ? hi :
                   es_alu_result;
always @ (posedge clk) begin
  if(mult_div != 4'b0000 | mf_mt[2] == 1'b1)begin
        if(mult_div[1:0] != 2'b00) 
            lo_i <= mult_result_i[31: 0];
        else if(mult_div[3:2] != 2'b00) 
            lo_i<= div_result_i[31: 0];
        else if ( mf_mt[2])
            lo_i <= es_alu_src1;
   end
end
always @ (posedge clk) begin
   if(mult_div != 4'b0000 | mf_mt[3] == 1'b1)begin
     if(mult_div[1:0] != 2'b00)
       hi_i <= mult_result_i[63:32];
     else if(mult_div[3:2] != 2'b00)
        hi_i <= div_result_i[63:32];
     else if (mf_mt[3])
        hi_i <= es_alu_src1;//将rs号通用寄存器的值写入特数据存器HI
    end
end
assign EXE_result = es_result;

由于在缓存中增加了mf_mt、mult_div两个信号,所以要更改头文件中译码与缓存级之间缓存的位宽。
mycpu.h

  `define DS_TO_ES_BUS_WD 145

总结

最终测试通过!

  • 17
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值