将陸續上傳本人寫的新書《自己動手寫CPU》,今天是第30篇,我盡量每周四篇
亞馬遜的銷售位址如下,歡迎大家圍觀呵!
http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4
China-pub的銷售位址如下:
http://product.china-pub.com/3804025
北發的銷售位址如下:
http://book.beifabook.com/Product/BookDetail.aspx?Plucode=712123950&extra=0_s25960657
7.8 修改OpenMIPS以實作乘累加、乘累減指令
7.8.1 修改譯碼階段的ID子產品
譯碼階段的ID子產品要添加對乘累加、乘累減指令的分析,根據圖7-11給出的指令格式可知,這4條指令都是SPECIAL2類指令,可以依據功能碼确定是哪一種指令,确定指令的過程如圖7-13所示。
其中涉及的宏定義如下,正是圖7-13中各個指令的功能碼。在本書附帶CD光牒Code\Chapter7_2目錄下的defines.v檔案中可以找到這些定義。
`define EXE_MADD 6'b000000
`define EXE_MADDU 6'b000001
`define EXE_MSUB 6'b000100
`define EXE_MSUBU 6'b000101
譯碼階段的ID子產品主要修改内容如下。完整代碼請參考本書CD光牒Code\Chapter7_2目錄下的id.v檔案。
module id(
......
);
......
assign stallreq = `NoStop;
always @ (*) begin
if (rst == `RstEnable) begin
......
end else begin
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
wd_o <= inst_i[15:11]; // 預設目的寄存器位址wd_o
wreg_o <= `WriteDisable;
instvalid <= `InstInvalid;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
reg1_addr_o <= inst_i[25:21]; // 預設的reg1_addr_o
reg2_addr_o <= inst_i[20:16]; // 預設的reg2_addr_o
imm <= `ZeroWord;
case (op)
......
`EXE_SPECIAL2_INST: begin // SPECIAL2類指令
case ( op3 )
......
`EXE_MADD: begin // madd指令
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MADD_OP;
alusel_o <= `EXE_RES_MUL;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_MADDU: begin // maddu指令
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MADDU_OP;
alusel_o <= `EXE_RES_MUL;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_MSUB: begin // msub指令
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MSUB_OP;
alusel_o <= `EXE_RES_MUL;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_MSUBU: begin // msubu指令
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MSUBU_OP;
alusel_o <= `EXE_RES_MUL;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
default: begin
end
endcase //EXE_SPECIAL_INST2 case
......
endmodule
這4條指令的譯碼過程都是相似的。簡單說明如下。
(1)因為最終結果都是寫入HI、LO寄存器,而不是寫入通用寄存器,是以設定wreg_o為WriteDisable。
(2)因為都要讀取兩個寄存器的值,是以設定reg1_read_o、reg2_read_o為1'b1,預設通過Regfile子產品讀端口1讀取的寄存器位址reg1_addr_o的值是指令的21-25bit,正是指令中的rs,預設通過Regfile子產品讀端口2讀取的寄存器位址reg2_addr_o的值是指令的16-20bit,正是指令中的rt。是以最終譯碼階段的輸出reg1_o就是位址為rs的寄存器的值,reg2_o就是位址為rt的寄存器的值。
(3)運算類型alusel_o的值都設定為EXE_RES_MUL,不過由于沒有要寫的通用寄存器,是以此處的alusel_o的值并沒有作用,也可以設定為EXE_RES_NOP。
(4)運算子類型aluop_o的值設定為與具體指令對應。
7.8.2 修改執行階段的EX子產品
參考圖7-12可知,EX子產品要增加4個接口,含義如表7-2所示。
EX子產品的代碼主要修改如下。完整代碼請參考本書附帶CD光牒Code\Chapter7_2目錄下的ex.v檔案。
module ex(
......
// 增加的輸入接口
input wire[`DoubleRegBus] hilo_temp_i,
input wire[1:0] cnt_i,
......
// 增加的輸出接口
output reg[`DoubleRegBus] hilo_temp_o,
output reg[1:0] cnt_o,
output reg stallreq
);
......
wire[`RegBus] opdata1_mult;
wire[`RegBus] opdata2_mult;
wire[`DoubleRegBus] hilo_temp;
reg[`DoubleRegBus] hilo_temp1;
reg stallreq_for_madd_msub;
......
/****************************************************************
*********** 第一段:計算乘法結果 *********
*****************************************************************/
//(1)取得乘法操作的被乘數,指令madd、msub都是有符号乘法,如果第一個
// 操作數reg1_i是負數,那麼取reg1_i的補碼作為被乘數,反之,直接
// 使用reg1_i作為被乘數
assign opdata1_mult = (((aluop_i == `EXE_MUL_OP) ||
(aluop_i == `EXE_MULT_OP) ||
(aluop_i == `EXE_MADD_OP) ||
(aluop_i == `EXE_MSUB_OP))&&
(reg1_i[31] == 1'b1)) ? (~reg1_i + 1) : reg1_i;
//(2)取得乘法操作的乘數,指令madd、msub是有符号乘法,如果第二個
// 操作數reg2_i是負數,那麼取reg2_i的補碼作為乘數,反之,直接
// 使用reg2_i作為乘數
assign opdata2_mult = (((aluop_i == `EXE_MUL_OP) ||
(aluop_i == `EXE_MULT_OP) ||
(aluop_i == `EXE_MADD_OP) ||
(aluop_i == `EXE_MSUB_OP))&&
(reg2_i[31] == 1'b1)) ? (~reg2_i + 1) : reg2_i;
//(3)得到臨時乘法結果,儲存在變量hilo_temp中
assign hilo_temp = opdata1_mult * opdata2_mult;
//(4)對臨時乘法結果進行修正,最終的乘法結果儲存在變量mulres中,有兩種情況:
// A、如果是有符号乘法運算madd、msub,那麼需要修正臨時乘法結果,如下:
// A1、如果被乘數與乘數,兩者一正一負,那麼需要對臨時乘法結果
// hilo_temp求補碼,作為最終的乘法結果,賦給變量mulres。
// A2、如果被乘數與乘數同号,那麼hilo_temp的值就作為mulres
// 的值。
// B、如果是無符号乘法運算maddu、msubu,那麼hilo_temp的值就作為
// 最終的乘法結果,賦給變量mulres。
always @ (*) begin
if(rst == `RstEnable) begin
mulres <= {`ZeroWord,`ZeroWord};
end else if ((aluop_i == `EXE_MULT_OP) || (aluop_i == `EXE_MUL_OP) ||
(aluop_i == `EXE_MADD_OP) || (aluop_i == `EXE_MSUB_OP)) begin
if(reg1_i[31] ^ reg2_i[31] == 1'b1) begin
mulres <= ~hilo_temp + 1;
end else begin
mulres <= hilo_temp;
end
end else begin
mulres <= hilo_temp;
end
end
/****************************************************************
*********** 第二段:乘累加、乘累減 *********
*****************************************************************/
// MADD、MADDU、MSUB、MSUBU指令
always @ (*) begin
if(rst == `RstEnable) begin
hilo_temp_o <= {`ZeroWord,`ZeroWord};
cnt_o <= 2'b00;
stallreq_for_madd_msub <= `NoStop;
end else begin
case (aluop_i)
`EXE_MADD_OP, `EXE_MADDU_OP: begin // madd、maddu指令
if(cnt_i == 2'b00) begin // 執行階段第一個時鐘周期
hilo_temp_o <= mulres;
cnt_o <= 2'b01;
hilo_temp1 <= {`ZeroWord,`ZeroWord};
stallreq_for_madd_msub <= `Stop;
end else if(cnt_i == 2'b01) begin // 執行階段第二個時鐘周期
hilo_temp_o <= {`ZeroWord,`ZeroWord};
cnt_o <= 2'b10;
hilo_temp1 <= hilo_temp_i + {HI,LO};
stallreq_for_madd_msub <= `NoStop;
end
end
`EXE_MSUB_OP, `EXE_MSUBU_OP: begin // msub、msubu指令
if(cnt_i == 2'b00) begin // 執行階段第一個時鐘周期
hilo_temp_o <= ~mulres + 1 ;
cnt_o <= 2'b01;
stallreq_for_madd_msub <= `Stop;
end else if(cnt_i == 2'b01)begin // 執行階段第二個時鐘周期
hilo_temp_o <= {`ZeroWord,`ZeroWord};
cnt_o <= 2'b10;
hilo_temp1 <= hilo_temp_i + {HI,LO};
stallreq_for_madd_msub <= `NoStop;
end
end
default: begin
hilo_temp_o <= {`ZeroWord,`ZeroWord};
cnt_o <= 2'b00;
stallreq_for_madd_msub <= `NoStop;
end
endcase
end
end
/****************************************************************
*********** 第三段:暫停流水線 *********
*****************************************************************/
// 目前隻有乘累加、乘累減指令會導緻流水線暫停,是以stallreq就直接等于
// stallreq_for_madd_msub的值
always @ (*) begin
stallreq = stallreq_for_madd_msub;
end
......
/****************************************************************
*********** 第四段:修改HI、LO寄存器的寫資訊 ********
*****************************************************************/
always @ (*) begin
if(rst == `RstEnable) begin
whilo_o <= `WriteDisable;
hi_o <= `ZeroWord;
lo_o <= `ZeroWord;
end else if((aluop_i == `EXE_MSUB_OP) || (aluop_i == `EXE_MSUBU_OP)) begin
whilo_o <= `WriteEnable;
hi_o <= hilo_temp1[63:32];
lo_o <= hilo_temp1[31:0];
end else if((aluop_i == `EXE_MADD_OP) ||
(aluop_i == `EXE_MADDU_OP)) begin
whilo_o <= `WriteEnable;
hi_o <= hilo_temp1[63:32];
lo_o <= hilo_temp1[31:0];
......
endmodule
上述代碼可以分為四段了解。
(1)第一段:計算從通用寄存器中讀出的兩個寄存器的乘法結果,儲存在mulres中。
(2)第二段:以乘累加指令為例進行講解。乘累減指令與此類似。
- 如果cnt_i為2'b00,表示是乘累加指令的第一個執行周期,此時将乘法結果mulres通過接口hilo_temp_o輸出到EX/MEM子產品,以便在下一個時鐘周期使用。同時,設定變量stallreq_for_madd_msub為Stop,表示乘累加指令請求流水線暫停。
- 如果cnt_i為2'b01,表示是乘累加指令的第二個執行周期,此時EX子產品的輸入hilo_temp_i就是上一個時鐘周期得到的乘法結果,是以将hilo_temp_i與HI、LO寄存器的值相加,得到最終運算結果,儲存到變量hilo_temp1中。同時,設定變量stallreq_for_madd_msub為NoStop,表示乘累加指令執行結束,不再請求流水線暫停。最後,設定cnt_o為2'b10,而不是直接設定為2'b00,目的是:如果因其它原因導緻流水線保持暫停,那麼由于cnt_o為2'b10,是以EX階段不再計算,進而防止乘累加指令重複運作。
(3)第三段:給出信号stallreq的值,目前隻有乘累加、乘累減指令會導緻流水線暫停,是以stallreq就直接等于變量stallreq_for_madd_msub的值。
(4)第四段:由于乘累加、乘累減指令要将最終結果寫入HI、LO寄存器,是以在第四段給出了對HI、LO寄存器的寫資訊。
7.8.3 修改EX/MEM子產品
參考圖7-12可知,EX/MEM子產品要增加4個接口,含義如表7-3所示。
EX/MEM子產品的代碼主要修改如下。完整代碼位于本書附帶CD光牒Code\Chapter7_2目錄下的ex_mem.v檔案。
module ex_mem(
......
// 來自控制子產品的資訊
input wire[5:0] stall,
......
// 增加的輸入接口
input wire[`DoubleRegBus] hilo_i,
input wire[1:0] cnt_i,
......
// 增加的輸出接口
output reg[`DoubleRegBus] hilo_o,
output reg[1:0] cnt_o
);
// 在流水線執行階段暫停的時候,将輸入信号hilo_i通過輸出接口hilo_o送出、
// 輸入信号cnt_i通過輸出接口cnt_o送出。其餘時刻,hilo_o為0,cnt_o
// 也為0。
always @ (posedge clk) begin
if(rst == `RstEnable) begin
......
hilo_o <= {`ZeroWord, `ZeroWord};
cnt_o <= 2'b00;
end else if(stall[3] == `Stop && stall[4] == `NoStop) begin
......
hilo_o <= hilo_i;
cnt_o <= cnt_i;
end else if(stall[3] == `NoStop) begin
......
hilo_o <= {`ZeroWord, `ZeroWord};
cnt_o <= 2'b00;
end else begin
hilo_o <= hilo_i;
cnt_o <= cnt_i;
end
end
endmodule
7.8.4 修改OpenMIPS子產品
因為上面為EX、EX/MEM子產品添加了接口,是以需要修改OpenMIPS子產品,以将這些接口連接配接起來,連接配接關系如圖7-12所示,具體代碼不在書中列出,讀者可以參考本書附帶CD光牒Code\Chapter7_2目錄下的openmips.v檔案。
代碼下載下傳位址http://download.csdn.net/detail/leishangwen/7858701