文章目录
一、算术逻辑运算类指令的添加
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寄存器。对于MTHI 和 MTLO指令来说,它们没有来自寄存器的源操作数,故对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
总结
最终测试通过!