【計算機系統設計】實踐筆記(2)資料通路構建:第一類R型指令分析(2)

夜路獨行者發表於2020-11-27

接上一篇
【計算機系統設計】實踐筆記(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才能進行帶符號數運算

  1. 對於slt sltu,前者是帶符號數比較,後者是無符號數比較,只有當比較的值顯式地宣告為signed時候,slt使用比較運算子<才進行帶符號比較,否則還是無符號比較。
  2. 對於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 測試我們的資料通路

  1. 由於暫存器堆初始全是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了。

  1. 寫入一些指令,測試我們的資料通路

測試檔案如下

`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

注意事項

  1. sllv srlv srav指令的使用方法是sllv rd,rt,rs它們的rs和rt與一般指令順序不一樣,是反著的,使用的時候需要注意
  2. 第一條指令使用nop是因為PC預設地址是0,此時ROM將會讀出0號地址的指令,這裡其實不寫空指令也無妨
  3. 注意ROM指令讀取延遲1個週期
  4. 此處有兩個負數驗證的指令,暫時沒有驗證,以後再說
  5. 敲重點!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發出的時間不一樣,是可以排隊的,沒關係的,不會超車插隊,另外,資料變化需要時間的,暫時淺顯理解即可。

相關文章