【計算機系統設計】實踐筆記(2)資料通路構建:第一類R型指令分析(2)
接上一篇
【計算機系統設計】實踐筆記(2)資料通路構建:第一類R型指令分析(1)
8.2 ALU運算器
`timescale 1ns / 1ps
//
// Engineer:jht
// Create Date: 2020/11/14 22:30:23
// Module Name: ALU_1
//
module ALU_1(
// data
input [31:0] A,
input [31:0] B,
// control
input [3:0] ALUop,
output reg [31:0] ALUresult
);
// convert A and B to signed numbers
wire signed [31:0] A_signed = A;
wire signed [31:0] B_signed = B;
always @(*)
begin
case (ALUop)
4'b0000: // add
begin
ALUresult <= A + B;
end
4'b0001: // addu
begin
ALUresult <= A + B;
end
4'b0010: // sub
begin
ALUresult <= A - B;
end
4'b0011: // subu
begin
ALUresult <= A - B;
end
4'b0100: // and
begin
ALUresult <= A & B;
end
4'b0101: // or
begin
ALUresult <= A | B;
end
4'b0110: // xor
begin
ALUresult <= A ^ B;
end
4'b0111: // nor
begin
ALUresult <= ~(A | B);
end
4'b1000: // slt // note:********signed********//
begin
if(A_signed < B_signed)
ALUresult <= 1;
else
ALUresult <= 0;
end
4'b1001: // sltu
begin
if(A < B)
ALUresult <= 1;
else
ALUresult <= 0;
end
4'b1010: // sllv
begin
ALUresult <= A << B;
end
4'b1011: // srlv
begin
ALUresult <= A >> B;
end
4'b1100: // srav // note: ******signed*******//
begin
ALUresult <= A_signed >>> B;
end
default:
begin
ALUresult <= 0;
end
endcase
end
endmodule
測試檔案tb_ALU_1.v
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/27 10:36:19
// Design Name:
// Module Name: tb_ALU_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_ALU_1;
// ALU_1 Inputs
reg [31:0] A = 0 ;
reg [31:0] B = 0 ;
reg [3:0] ALUop = 0 ;
// ALU_1 Outputs
wire [31:0] ALUresult ;
ALU_1 u_ALU_1 (
.A ( A [31:0] ),
.B ( B [31:0] ),
.ALUop ( ALUop [3:0] ),
.ALUresult ( ALUresult [31:0] )
);
initial
begin
#10
ALUop = 0;
A = 1;
B = 4;
#10
ALUop = 1;
A = 1;
B = 5;
#10
ALUop = 2;
A = 4;
B = 1;
#10
ALUop = 3;
A = 4;
B = 2;
// and
#10
ALUop = 4;
A = 32'b1001111;
B = 32'b1001001;
#10
ALUop = 5;
A = 32'b1001111;
B = 32'b1001001;
#10
ALUop = 6;
A = 32'b1001111;
B = 32'b1001001;
#10
ALUop = 7;
A = 32'b1001111;
B = 32'b1001001;
// slt
#30
ALUop = 8;
A = -1;
B = 3;
#10
ALUop = 9;
A = -1;
B = 3;
#10
ALUop = 9;
A = 1;
B = 3;
// sllv
#30
ALUop = 10;
A = 32'b1001111;
B = 32'd4;
#10
ALUop = 11;
A = 32'hABCDabcd;
B = 32'd4;
// srav
#30
ALUop = 12;
A = 32'hABCDabcd;
B = 32'd4;
#40
ALUop = 4'b1111;
end
endmodule
功能模擬成功!
8.2.1 注意事項:有無符號數的運算和比較
主要針對slt sltu srlv srav
這幾條指令中,涉及到的對有無符號數進行的操作。
原則:Verilog預設都是無符號數,需要顯式地宣告signed
才能進行帶符號數運算。
- 對於
slt sltu
,前者是帶符號數比較,後者是無符號數比較,只有當比較的值顯式地宣告為signed
時候,slt
使用比較運算子<
才進行帶符號比較,否則還是無符號比較。 - 對於
srlv srav
,主要是srav
,對於帶符號數,使用>>>
首位生成1
,通用應該顯式地宣告signed
否則會與srlv
指令沒區別。
注意程式碼中的
// convert A and B to signed numbers
wire signed [31:0] A_signed = A;
wire signed [31:0] B_signed = B;
這是將無符號數宣告為帶符號數的方法。
8.3 Register Files 暫存器堆
reg_files.v
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/14 22:31:09
// Design Name:
// Module Name: reg_files_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module reg_files_1(
input clk,
input rst_n,
/*** read port 1 ***/
input [4:0] rA, // rs field
output reg [31:0] A,
/*** read port 2 ***/
input [4:0] rB, // rt
output reg [31:0] B,
/*** write port ***/
input [4:0] rW, // rd or rt
input [31:0] writeData, // data
input RegWrite // if RegWrite == 1,you can write data to reg files
);
// reg files
reg [31:0] register [0:31];
integer i;
initial
begin
for (i = 0;i < 32;i = i + 1)
begin
register[i] <= 0;
end
end
/******* write operation *******/
always @(posedge clk) // sequential logic
begin
if(rst_n == 0) // reset is invalid
begin
if((RegWrite == 1'b1) && (rW != 5'b0)) // write is valid and address is not equal zero
begin
register[rW] <= writeData;
end
else
;
end
else
;
end
/******* rA read operation *******/
always @(*) // combinational logic
begin
if(rst_n == 1)
begin
A <= 32'b0;
end
else if(rA == 5'b0)
begin
A <= 32'b0;
end
else
begin
A <= register[rA];
end
end
/******* rB read operation *******/
always @(*) // combinational logic
begin
if(rst_n == 1)
begin
B <= 32'b0;
end
else if(rB == 5'b0) // $zero
begin
B <= 32'b0;
end
else
begin
B <= register[rB];
end
end
endmodule
測試檔案tb_reg_files_1.v
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/27 10:11:14
// Design Name:
// Module Name: tb_reg_files_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_reg_files_1;
// reg_files_1 Parameters
parameter PERIOD = 10;
// reg_files_1 Inputs
reg clk = 0 ;
reg rst_n = 1 ;
reg [4:0] rA = 0 ;
reg [4:0] rB = 0 ;
reg [4:0] rW = 0 ;
reg [31:0] writeData = 0 ;
reg RegWrite = 0 ;
// reg_files_1 Outputs
wire [31:0] A ;
wire [31:0] B ;
initial
begin
forever
#(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 0;
end
reg_files_1 u_reg_files_1 (
.clk ( clk ),
.rst_n ( rst_n ),
.rA ( rA [4:0] ),
.rB ( rB [4:0] ),
.rW ( rW [4:0] ),
.writeData ( writeData [31:0] ),
.RegWrite ( RegWrite ),
.A ( A [31:0] ),
.B ( B [31:0] )
);
initial
begin
#20
RegWrite = 1;
rW = 0;
writeData = 32'hff;
#10
rW = 1;
writeData = 32'hff;
#10
rA = 1;
#10
rB = 1;
#10
rA = 0;
rB = 0;
end
endmodule
初步功能模擬成功!
9 連線已有器件
9.1 增加ROM
使用IP核,參考東南大學計算機系統設計MOOC9.1節的做法。
9.2 將已有部件連線起來!
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/27 11:41:34
// Design Name:
// Module Name: datapath_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 僅僅實現了幾個簡單的R類指令的最簡單的資料通路,不與外界互動
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module datapath_1(
input clk,
input rst_n
);
/******** PC ********/
// pc_1 Outputs
wire [31:0] pcOld;
pc_1 u_pc_1 (
.clk ( clk ),
.rst_n ( rst_n ),
.pcNew ( pcOld ), // pcNew = pcOld + 4; no selection
.pcOld ( pcOld )
);
/******** Instruction ROM ********/
// blk_mem_gen_0 Inputs
wire [13:0] addra = pcOld[15:2];
// blk_mem_gen_0 Outputs // instructions
wire [31:0] instruction;
blk_mem_gen_0 u_blk_mem_gen_0 (
.clka ( clk ),
.addra ( addra ),
.douta ( instruction )
);
/******** Reg Files ********/
// reg_files_1 Inputs
wire [4:0] rA = instruction[25:21];
wire [4:0] rB = instruction[20:16];
wire [4:0] rW = instruction[15:11];
wire [31:0] writeData;
wire RegWrite;
// reg_files_1 Outputs
wire [31:0] A;
wire [31:0] B;
reg_files_1 u_reg_files_1 (
.clk ( clk ),
.rst_n ( rst_n ),
.rA ( rA ),
.rB ( rB ),
.rW ( rW ),
.writeData ( writeData ),
.RegWrite ( RegWrite ),
.A ( A ),
.B ( B )
);
/******** ALU ********/
// ALU_1 Inputs
// wire [31:0] A;
// wire [31:0] B;
wire [3:0] ALUop;
// ALU_1 Outputs
wire [31:0] ALUresult = writeData;
ALU_1 u_ALU_1 (
.A ( A ),
.B ( B ),
.ALUop ( ALUop ),
.ALUresult ( ALUresult )
);
/******** controler ********/
// control_1 Inputs
wire [5:0] op = instruction[31:26];
wire [5:0] func = instruction[5:0];
// control_1 Outputs
// wire RegWrite
// wire [3:0] ALUop;
control_1 u_control_1 (
.op ( op ),
.func ( func ),
.RegWrite ( RegWrite ),
.ALUop ( ALUop )
);
endmodule
9.3 測試我們的資料通路
- 由於暫存器堆初始全是0,最開始又沒有寫入的指令,因此修改下以方便測試
// reg files
reg [31:0] register [0:31];
integer i;
initial
begin
for (i = 0;i < 32;i = i + 1)
begin
/
/ 為了方便初步測試 ///
register[i] <= i;
end
end
注意賦值的是i
而不是0
了。
- 寫入一些指令,測試我們的資料通路
測試檔案如下
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/27 12:12:14
// Design Name:
// Module Name: tb_datapath_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_datapath_1;
// datapath_1 Parameters
parameter PERIOD = 10;
// datapath_1 Inputs
reg clk = 0 ;
reg rst_n = 1 ;
// datapath_1 Outputs
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 0;
end
datapath_1 u_datapath_1 (
.clk ( clk ),
.rst_n ( rst_n )
);
endmodule
RTL優化
測試指令如下
以上錯誤!因為一次性連線了太多器件,不符合單元測試原則,重新開始,重要的是,ROM的IP核沒有測試!
9.4 構建取值模組
我們先把PC和ROM連線起來測試。
然後發現……很詭異,PC似乎對ROM不起作用?
這個IP核……居然有延遲??不是瞬間取得指令……需要等一個週期後,再等待上升沿,才能取指。也就是說,ROM的取指需要等待一個額外的時鐘週期,這才是真實世界。
9.5 插敘:帶延遲的ROM
實際上,訪存時間更長,取指比較慢,這個事實我們都知道,現在,我們真地面臨這個問題了。
理想取指的時序圖,與帶一個時鐘週期延遲的時序圖,是不一樣的!
在單週期CPU中
- 理想瞬間取指,那麼更新PC值需要是下降沿,而寫暫存器堆需要是上升沿
- 帶一個時鐘週期延遲的,就可以都是上升沿,因為取下一條指令的過程佔一個時鐘週期,此時CPU就講當前指令執行完了,也就是取下一條指令和CPU執行當前指令,同步進行。
特別注意:預設值0的指令會比較詭異
在我們的設計中,pc預設是0,因此……0號地址的指令會被直接取出來,但是如果沒有復位也是不能指令的,這個情況下,其實可以用nop
指令(全是0)作為0號地址的指令。
9.6 完整資料通路的實現與測試
讓我們返回看看。
datapath_1.v
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/27 11:41:34
// Design Name:
// Module Name: datapath_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 僅僅實現了幾個簡單的R類指令的最簡單的資料通路,不與外界互動
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module datapath_1(
input clk,
input rst_n
);
/******** PC ********/
// pc_1 Outputs
wire [31:0] pcOld;
pc_1 u_pc_1 (
.clk ( clk ),
.rst_n ( rst_n ),
.pcNew ( pcOld ), // pcNew = pcOld + 4; no selection
.pcOld ( pcOld )
);
/******** Instruction ROM ********/
// blk_mem_gen_0 Inputs
wire [13:0] addra = pcOld[15:2];
// blk_mem_gen_0 Outputs // instructions
wire [31:0] instruction;
blk_mem_gen_0 u_blk_mem_gen_0 (
.clka ( clk ),
.addra ( addra ),
.douta ( instruction )
);
/******** Reg Files ********/
// reg_files_1 Inputs
wire [4:0] rA = instruction[25:21];
wire [4:0] rB = instruction[20:16];
wire [4:0] rW = instruction[15:11];
wire [31:0] writeData;
wire RegWrite;
// reg_files_1 Outputs
wire [31:0] A;
wire [31:0] B;
reg_files_1 u_reg_files_1 (
.clk ( clk ),
.rst_n ( rst_n ),
.rA ( rA ),
.rB ( rB ),
.rW ( rW ),
.writeData ( writeData ),
.RegWrite ( RegWrite ),
.A ( A ),
.B ( B )
);
/******** ALU ********/
// ALU_1 Inputs
// wire [31:0] A;
// wire [31:0] B;
wire [3:0] ALUop;
// ALU_1 Outputs
// wire [31:0] ALUresult = writeData;【】【為什麼不能用?】
ALU_1 u_ALU_1 (
.A ( A ),
.B ( B ),
.ALUop ( ALUop ),
.ALUresult ( writeData )
);
/******** controler ********/
// control_1 Inputs
wire [5:0] op = instruction[31:26];
wire [5:0] func = instruction[5:0];
// control_1 Outputs
// wire RegWrite
// wire [3:0] ALUop;
control_1 u_control_1 (
.op ( op ),
.func ( func ),
.RegWrite ( RegWrite ),
.ALUop ( ALUop )
);
endmodule
RTL優化
測試檔案 tb_datapath_1.v
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/27 12:12:14
// Design Name:
// Module Name: tb_datapath_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_datapath_1;
// datapath_1 Parameters
parameter PERIOD = 10;
// datapath_1 Inputs
reg clk = 0 ;
reg rst_n = 1 ;
// datapath_1 Outputs
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 0;
end
datapath_1 u_datapath_1 (
.clk ( clk ),
.rst_n ( rst_n )
);
endmodule
模擬結果
測試指令
nop
add $1,$2,$3 # $1 = 2 + 3 = 5
addu $2,$4,$1 # $2 = 4 + 5 = 9
sub $4,$2,$1 # $4 = 9 - 5 = 4
subu $5,$4,$3 # $5 = 4 - 3 = 1
and $6,$7,$8 # $6 = 0111 and 1000 = 0
or $7,$6,$8 # $7 = 0 or 1000 = 8
xor $7,$6,$8 # $7 = 0000 xor 1000 = 1000 = 8
nor $8,$7,$6 # $8 = not (1000 or 0) = 11111111111110111
slt $10,$11,$12 # $10 = 11 < 12 = 1 # 應該用負數驗證,以後再說
sltu $10,$12,$11 # $10 = 12 > 11 = 0
sllv $12,$5,$13 # $12 = 1101 << 1 = 1101_0 = 1A 【注意此處的倒置問題! sllv rd,rt,rs】
srlv $12,$5,$13 # $12 = 1101 >> 1 = 110 = 6
srav $14,$5,$15 # $14 = 1111 >>> 1 = 111 = 7 應該用負數驗證,以後再說
指令編碼
00000000
00430820
00811021
00412022
00832823
00e83024
00c83825
00c83826
00e64027
016c502a
018b502b
01a56004
01a56006
01e57007
注意事項
- sllv srlv srav指令的使用方法是
sllv rd,rt,rs
它們的rs和rt與一般指令順序不一樣,是反著的,使用的時候需要注意 - 第一條指令使用
nop
是因為PC預設地址是0,此時ROM將會讀出0號地址的指令,這裡其實不寫空指令也無妨 - 注意ROM指令讀取延遲1個週期
- 此處有兩個負數驗證的指令,暫時沒有驗證,以後再說
- 敲重點!writeData與ALUresult的連線問題
10 驚人的事實:我們已經構建了完整的資料通路
你可能感到驚訝,但這就是事實,我們已經,構建好了一個CPU,並且它能夠執行13條指令!
這簡直太酷了不是嗎!難以想象……你可能會說?這……就完成了?是的沒錯,如果我們只需要13條指令的CPU,並且不需要與外界互動的話,真的已經完成了,當然……這個CPU沒什麼價值,不過後續我們會改進它的不是嗎?這很有趣的!
我們會一步步地完成一個完整的CPU,最終變成五級流水線CPU,這簡直太棒了!讓我們一起加油!
來看看示意圖,注意,只是示意圖,pc的位寬並不是標準的32位而是8位,總之,這就是完整的資料通路了。
只不過這個CPU還不能與外界互動……但是,它的確能夠執行指令了不是嗎?後續我們慢慢改進就是了。
10.1 執行我們的第一個CPU
在上面我們已經執行測試過了,不再重複。
10.2 值得優化的點
在有些時候,我們的指令沒有準備好,但是Reg已經讀取出去了,可能有隱患,我們可以給Reg Files加上讀使能訊號,但是本次不加了。
11 資料流建模傳輸問題:不止連線
經驗教訓,RTL建模看不出來傳輸方向! 行為模擬很必要呀!
12 取指延遲
我們都知道,訪存是很慢的(相比於CPU執行),在本次示例中,ROM取指,需要2個時鐘週期,因此,我們的單週期CPU,更新PC和更新暫存器堆,都可以上升沿觸發。
PC也能夠儲存下一條指令的地址,在取下一條指令的同時,當前指令也在執行,取完下一條指令,當前指令也執行完成了。
我們看時序圖,從PC更新,到取得PC對應的指令,需要2個時鐘週期。
也就是說,向記憶體發出指令地址之後,需要兩個時鐘週期,指令才能被取得,在此期間,CPU內的指令還是原來的指令,該指令也已經在一個時鐘週期執行完成,執行一條指令需要三個時鐘週期。
你可能疑惑,那pc每個時鐘週期更新一次,兩個時鐘週期才能夠取到指令,不會衝突嗎?當然不會。想象一下高速公路的汽車! pc發出的時間不一樣,是可以排隊的,沒關係的,不會超車插隊,另外,資料變化需要時間的,暫時淺顯理解即可。
相關文章
- 計算機系統5-> 計組與體系結構2 | MIPS指令集(上)| 指令系統計算機
- 系統架構設計筆記(87)—— 計算機病毒與防治架構筆記計算機
- 系統架構設計筆記(105)—— 雲端計算架構筆記
- 系統架構設計筆記(97)—— 資料包架構筆記
- 《資料密集型應用系統設計》筆記筆記
- 微機原理與系統設計筆記2 | 8086CPU結構與功能筆記
- 設計模式筆記(2)設計模式筆記
- 第2課筆記 linux系統指令筆記Linux
- Hadoop高階資料分析 使用Hadoop生態系統設計和構建大資料系統Hadoop大資料
- 2萬字揭秘阿里資料治理建設實踐阿里
- 低程式碼實時數倉構建系統的設計與實踐
- 設計專案全生命週期管理系統構建與實踐
- vivo 實時計算平臺建設實踐
- 安全防護系統構設計與實踐
- 微機原理與系統設計筆記6 | 儲存器系統設計筆記
- 微機原理與系統設計筆記4 | 組合語言程式設計與其他指令筆記組合語言程式設計
- 極光筆記丨資料質量建設實踐筆記
- 指令集體系結構_計算機體系結構:指令程式碼計算機
- 《Redis設計與實現》筆記 -- 資料結構與物件Redis筆記資料結構物件
- 讀資料工程之道:設計和構建健壯的資料系統29分析
- (計算機體系結構)MIPS指令集結構計算機
- B站萬億級資料庫選型與架構設計實踐資料庫架構
- 億級流量系統架構之如何設計高容錯分散式計算系統【石杉的架構筆記】架構分散式筆記
- 計算機演算法設計與分析筆記(二)——遞迴與分治計算機演算法筆記遞迴
- React共享單車後臺管理系統開發(記錄筆記2)——主頁面架構設計React筆記架構
- 系統架構設計筆記(95)—— TCP 協議架構筆記TCP協議
- 系統架構設計筆記(104)—— 虛擬化架構筆記
- 系統架構設計筆記(106)—— 物聯網架構筆記
- 深入理解計算機系統-學習筆記 (1)計算機筆記
- 讀資料工程之道:設計和構建健壯的資料系統09示例和型別型別
- 企業級大資料架構設計【2】大資料架構
- 深入理解計算機系統系列(第一章--計算機系統漫遊)計算機
- 《計算機體系結構:量化研究方法》讀書筆記計算機筆記
- 簡讀筆記_Redis設計與實現_第一章_資料結構與物件筆記Redis資料結構物件
- 實踐:GNU構建系統
- 如何構建設計語言系統
- “淘寶京東”構建流式計算賣家日誌系統架構的應用實踐架構
- 02《構建之法》閱讀筆記_2筆記