Verilog 流水線

2022-05-18 10:23 更新

關(guān)鍵詞:流水線,乘法器

硬件描述語(yǔ)言的一個(gè)突出優(yōu)點(diǎn)就是指令執(zhí)行的并行性。多條語(yǔ)句能夠在相同時(shí)鐘周期內(nèi)并行處理多個(gè)信號(hào)數(shù)據(jù)。

但是當(dāng)數(shù)據(jù)串行輸入時(shí),指令執(zhí)行的并行性并不能體現(xiàn)出其優(yōu)勢(shì)。而且很多時(shí)候有些計(jì)算并不能在一個(gè)或兩個(gè)時(shí)鐘周期內(nèi)執(zhí)行完畢,如果每次輸入的串行數(shù)據(jù)都需要等待上一次計(jì)算執(zhí)行完畢后才能開啟下一次的計(jì)算,那效率是相當(dāng)?shù)偷?。流水線就是解決多周期下串行數(shù)據(jù)計(jì)算效率低的問題。

流水線

流水線的基本思想是:把一個(gè)重復(fù)的過(guò)程分解為若干個(gè)子過(guò)程,每個(gè)子過(guò)程由專門的功能部件來(lái)實(shí)現(xiàn)。將多個(gè)處理過(guò)程在時(shí)間上錯(cuò)開,依次通過(guò)各功能段,這樣每個(gè)子過(guò)程就可以與其他子過(guò)程并行進(jìn)行。

假如一個(gè)洗衣店內(nèi)洗衣服的過(guò)程分為 4 個(gè)階段:取衣、洗衣、烘干、裝柜。每個(gè)階段都需要半小時(shí)來(lái)完成,則洗一次衣服需要 2 小時(shí)。

考慮最差情況,洗衣店內(nèi)只有一臺(tái)洗衣機(jī)、一臺(tái)烘干機(jī)、一個(gè)衣柜。如果每半小時(shí)送來(lái)一批要洗的衣服,每次等待上一批衣服洗完需要 2 小時(shí),那么洗完 4 批衣服需要的時(shí)間就是 8 小時(shí)。

圖示如下:


對(duì)這個(gè)洗衣店的裝備進(jìn)行升級(jí),一共引進(jìn) 4 套洗衣服的裝備,工作人員也增加到 4 個(gè),每個(gè)人負(fù)責(zé)一個(gè)洗衣階段。所以每批次的衣服,都能夠及時(shí)的被相同的人放入到不同的洗衣機(jī)內(nèi)。由于時(shí)間上是錯(cuò)開的,每批次的衣服都能被相同的人在不同的設(shè)備與時(shí)間段(半小時(shí))內(nèi)洗衣、烘干和裝柜。圖示如下。


可以看出,洗完 4 批衣服只需要 3 個(gè)半小時(shí),效率明顯提高。

其實(shí),在 2 小時(shí)后第一套洗衣裝備已經(jīng)完成洗衣過(guò)程而處于空閑狀態(tài),如果此時(shí)還有第 5 批衣服的送入,那么第一套設(shè)備又可以開始工作。依次類推,只要衣服批次不停的輸入,4 臺(tái)洗衣設(shè)備即可不間斷的完成對(duì)所有衣服的清洗過(guò)程。且除了第一批次洗衣時(shí)間需要 2 小時(shí),后面每半小時(shí)都會(huì)有一批次衣服清洗完成。

衣服批次越多,節(jié)省的時(shí)間就越明顯。假如有 N 批次衣服,需要的時(shí)間為 (4+N) 個(gè)半小時(shí)。

當(dāng)然,升級(jí)后洗衣流程也有缺點(diǎn)。設(shè)備和工作人員的增加導(dǎo)致了投入的成本增加,洗衣店內(nèi)剩余空間也被縮小,工作狀態(tài)看起來(lái)比較繁忙。

和洗衣服過(guò)程類似,數(shù)據(jù)的處理路徑也可以看作是一條生產(chǎn)線,路徑上的每個(gè)數(shù)字處理單元都可以看作是一個(gè)階段,會(huì)產(chǎn)生延時(shí)。

流水線設(shè)計(jì)就是將路徑系統(tǒng)的分割成一個(gè)個(gè)數(shù)字處理單元(階段),并在各個(gè)處理單元之間插入寄存器來(lái)暫存中間階段的數(shù)據(jù)。被分割的單元能夠按階段并行的執(zhí)行,相互間沒有影響。所以最后流水線設(shè)計(jì)能夠提高數(shù)據(jù)的吞吐率,即提高數(shù)據(jù)的處理速度。

流水線設(shè)計(jì)的缺點(diǎn)就是,各個(gè)處理階段都需要增加寄存器保存中間計(jì)算狀態(tài),而且多條指令并行執(zhí)行,勢(shì)必會(huì)導(dǎo)致功耗增加。

下面,設(shè)計(jì)一個(gè)乘法器,并對(duì)是否采用流水線設(shè)計(jì)進(jìn)行對(duì)比。

一般乘法器設(shè)計(jì)

前言

也許有人會(huì)問,直接用乘號(hào) ?*? 來(lái)完成 2 個(gè)數(shù)的相乘不是更快更簡(jiǎn)單嗎?

如果你有這個(gè)疑問,說(shuō)明你對(duì)硬件描述語(yǔ)言的認(rèn)知還有所不足。就像之前所說(shuō),Verilog 描述的是硬件電路,直接用乘號(hào)完成相乘過(guò)程,編譯器在編譯的時(shí)候也會(huì)把這個(gè)乘法表達(dá)式映射成默認(rèn)的乘法器,但其構(gòu)造不得而知。

例如,在 FPGA 設(shè)計(jì)中,可以直接調(diào)用 IP 核來(lái)生成一個(gè)高性能的乘法器。在位寬較小的時(shí)候,一個(gè)周期內(nèi)就可以輸出結(jié)果,位寬較大時(shí)也可以流水輸出。在能滿足要求的前提下,可以謹(jǐn)慎的用 ?*? 或直接調(diào)用 IP 來(lái)完成乘法運(yùn)算。

但乘法器 IP 也有很多的缺陷,例如位寬的限制,未知的時(shí)序等。尤其使用乘號(hào),會(huì)為數(shù)字設(shè)計(jì)的不確定性埋下很大的隱瞞。

很多時(shí)候,常數(shù)的乘法都會(huì)用移位相加的形式實(shí)現(xiàn),例如:

A = A<<1 ;       //完成A * 2
A = (A<<1) + A ;   //對(duì)應(yīng)A * 3
A = (A<<3) + (A<<2) + (A<<1) + A ; //對(duì)應(yīng)A * 15

用一個(gè)移位寄存器和一個(gè)加法器就能完成乘以 3 的操作。但是乘以 15 時(shí)就需要 3 個(gè)移位寄存器和 3 個(gè)加法器(當(dāng)然乘以 15 可以用移位相減的方式)。

有時(shí)候數(shù)字電路在一個(gè)周期內(nèi)并不能夠完成多個(gè)變量同時(shí)相加的操作。所以數(shù)字設(shè)計(jì)中,最保險(xiǎn)的加法操作是同一時(shí)刻只對(duì) 2 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算,最差設(shè)計(jì)是同一時(shí)刻對(duì) 4 個(gè)及以上的數(shù)據(jù)進(jìn)行加法運(yùn)算。

如果設(shè)計(jì)中有同時(shí)對(duì) 4 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算的操作設(shè)計(jì),那么此部分設(shè)計(jì)就會(huì)有危險(xiǎn),可能導(dǎo)致時(shí)序不滿足。

此時(shí),設(shè)計(jì)參數(shù)可配、時(shí)序可控的流水線式乘法器就顯得有必要了。

設(shè)計(jì)原理

和十進(jìn)制乘法類似,計(jì)算 13 與 5 的相乘過(guò)程如下所示:


由此可知,被乘數(shù)按照乘數(shù)對(duì)應(yīng) bit 位進(jìn)行移位累加,便可完成相乘的過(guò)程。

假設(shè)每個(gè)周期只能完成一次累加,那么一次乘法計(jì)算時(shí)間最少的時(shí)鐘數(shù)恰好是乘數(shù)的位寬。所以建議,將位寬窄的數(shù)當(dāng)做乘數(shù),此時(shí)計(jì)算周期短。

乘法器設(shè)計(jì)

考慮每次乘法運(yùn)算只能輸出一個(gè)結(jié)果(非流水線設(shè)計(jì)),設(shè)計(jì)代碼如下。

module    mult_low
    #(parameter N=4,
      parameter M=4)
     (
      input                     clk,
      input                     rstn,
      input                     data_rdy ,  //數(shù)據(jù)輸入使能
      input [N-1:0]             mult1,      //被乘數(shù)
      input [M-1:0]             mult2,      //乘數(shù)

      output                    res_rdy ,   //數(shù)據(jù)輸出使能
      output [N+M-1:0]          res         //乘法結(jié)果
      );

    //calculate counter
    reg [31:0]           cnt ;
    //乘法周期計(jì)數(shù)器
    wire [31:0]          cnt_temp = (cnt == M)? 'b0 : cnt + 1'b1 ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            cnt    <= 'b0 ;
        end
        else if (data_rdy) begin    //數(shù)據(jù)使能時(shí)開始計(jì)數(shù)
            cnt    <= cnt_temp ;
        end
        else if (cnt != 0 ) begin  //防止輸入使能端持續(xù)時(shí)間過(guò)短
            cnt    <= cnt_temp ;
        end
        else begin
            cnt    <= 'b0 ;
        end
    end

    //multiply
    reg [M-1:0]          mult2_shift ;
    reg [M+N-1:0]        mult1_shift ;
    reg [M+N-1:0]        mult1_acc ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            mult2_shift    <= 'b0 ;
            mult1_shift    <= 'b0 ;
            mult1_acc      <= 'b0 ;
        end
        else if (data_rdy && cnt=='b0) begin  //初始化
            mult1_shift    <= {{(N){1'b0}}, mult1} << 1 ;  
            mult2_shift    <= mult2 >> 1 ;  
            mult1_acc      <= mult2[0] ? {{(N){1'b0}}, mult1} : 'b0 ;
        end
        else if (cnt != M) begin
            mult1_shift    <= mult1_shift << 1 ;  //被乘數(shù)乘2
            mult2_shift    <= mult2_shift >> 1 ;  //乘數(shù)右移,方便判斷
            //判斷乘數(shù)對(duì)應(yīng)為是否為1,為1則累加
            mult1_acc      <= mult2_shift[0] ? mult1_acc + mult1_shift : mult1_acc ;
        end
        else begin
            mult2_shift    <= 'b0 ;
            mult1_shift    <= 'b0 ;
            mult1_acc      <= 'b0 ;
        end
    end

    //results
    reg [M+N-1:0]        res_r ;
    reg                  res_rdy_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            res_r          <= 'b0 ;
            res_rdy_r      <= 'b0 ;
        end  
        else if (cnt == M) begin
            res_r          <= mult1_acc ;  //乘法周期結(jié)束時(shí)輸出結(jié)果
            res_rdy_r      <= 1'b1 ;
        end
        else begin
            res_r          <= 'b0 ;
            res_rdy_r      <= 'b0 ;
        end
    end

    assign res_rdy       = res_rdy_r;
    assign res           = res_r;

endmodule

testbench

`timescale 1ns/1ns

module test ;
    parameter    N = 8 ;
    parameter    M = 4 ;
    reg          clk, rstn;
 
   //clock
    always begin
        clk = 0 ; #5 ;
        clk = 1 ; #5 ;
    end

   //reset
    initial begin
        rstn      = 1'b0 ;
        #8 ;      rstn      = 1'b1 ;
    end

    //no pipeline
    reg                  data_rdy_low ;
    reg [N-1:0]          mult1_low ;
    reg [M-1:0]          mult2_low ;
    wire [M+N-1:0]       res_low ;
    wire                 res_rdy_low ;

    //使用任務(wù)周期激勵(lì)
    task mult_data_in ;  
        input [M+N-1:0]   mult1_task, mult2_task ;
        begin
            wait(!test.u_mult_low.res_rdy) ;  //not output state
            @(negedge clk ) ;
            data_rdy_low = 1'b1 ;
            mult1_low = mult1_task ;
            mult2_low = mult2_task ;
            @(negedge clk ) ;
            data_rdy_low = 1'b0 ;
            wait(test.u_mult_low.res_rdy) ; //test the output state
        end
    endtask

    //driver
    initial begin
        #55 ;
        mult_data_in(25, 5 ) ;
        mult_data_in(16, 10 ) ;
        mult_data_in(10, 4 ) ;
        mult_data_in(15, 7) ;
        mult_data_in(215, 9) ;
    end

    mult_low  #(.N(N), .M(M))
    u_mult_low
    (
      .clk              (clk),
      .rstn             (rstn),
      .data_rdy         (data_rdy_low),
      .mult1            (mult1_low),
      .mult2            (mult2_low),
      .res_rdy          (res_rdy_low),
      .res              (res_low));

   //simulation finish
   initial begin
      forever begin
         #100;
         if ($time >= 10000)  $finish ;
      end
   end

endmodule // test

仿真結(jié)果如下。

由圖可知,輸入的 2 個(gè)數(shù)據(jù)在延遲 4 個(gè)周期后,得到了正確的相乘結(jié)果。算上中間送入數(shù)據(jù)的延遲時(shí)間,計(jì)算 4 次乘法大約需要 20 個(gè)時(shí)鐘周期。


流水線乘法器設(shè)計(jì)

下面對(duì)乘法執(zhí)行過(guò)程的中間狀態(tài)進(jìn)行保存,以便流水工作,設(shè)計(jì)代碼如下。

單次累加計(jì)算過(guò)程的代碼文件如下(mult_cell.v ):

module    mult_cell
    #(parameter N=4,
      parameter M=4)
    (
      input                     clk,
      input                     rstn,
      input                     en,
      input [M+N-1:0]           mult1,      //被乘數(shù)
      input [M-1:0]             mult2,      //乘數(shù)
      input [M+N-1:0]           mult1_acci, //上次累加結(jié)果

      output reg [M+N-1:0]      mult1_o,     //被乘數(shù)移位后保存值
      output reg [M-1:0]        mult2_shift, //乘數(shù)移位后保存值
      output reg [N+M-1:0]      mult1_acco,  //當(dāng)前累加結(jié)果
      output reg                rdy );

    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            rdy            <= 'b0 ;
            mult1_o        <= 'b0 ;
            mult1_acco     <= 'b0 ;
            mult2_shift    <= 'b0 ;
        end
        else if (en) begin
            rdy            <= 1'b1 ;
            mult2_shift    <= mult2 >> 1 ;
            mult1_o        <= mult1 << 1 ;
            if (mult2[0]) begin
                //乘數(shù)對(duì)應(yīng)位為1則累加
                mult1_acco  <= mult1_acci + mult1 ;  
            end
            else begin
                mult1_acco  <= mult1_acci ; //乘數(shù)對(duì)應(yīng)位為1則保持
            end
        end
        else begin
            rdy            <= 'b0 ;
            mult1_o        <= 'b0 ;
            mult1_acco     <= 'b0 ;
            mult2_shift    <= 'b0 ;
        end
    end

endmodule

頂層例化

多次模塊例化完成多次累加,代碼文件如下(mult_man.v ):

module    mult_man
    #(parameter N=4,
      parameter M=4)
    (
      input                     clk,
      input                     rstn,
      input                     data_rdy ,
      input [N-1:0]             mult1,
      input [M-1:0]             mult2,

      output                    res_rdy ,
      output [N+M-1:0]          res );

    wire [N+M-1:0]       mult1_t [M-1:0] ;
    wire [M-1:0]         mult2_t [M-1:0] ;
    wire [N+M-1:0]       mult1_acc_t [M-1:0] ;
    wire [M-1:0]         rdy_t ;

    //第一次例化相當(dāng)于初始化,不能用 generate 語(yǔ)句
    mult_cell      #(.N(N), .M(M))
    u_mult_step0
    (
      .clk              (clk),
      .rstn             (rstn),
      .en               (data_rdy),
      .mult1            ({{(M){1'b0}}, mult1}),
      .mult2            (mult2),
      .mult1_acci       ({(N+M){1'b0}}),
      //output
      .mult1_acco       (mult1_acc_t[0]),
      .mult2_shift      (mult2_t[0]),
      .mult1_o          (mult1_t[0]),
      .rdy              (rdy_t[0]) );

    //多次模塊例化,用 generate 語(yǔ)句
    genvar               i ;
    generate
        for(i=1; i<=M-1; i=i+1) begin: mult_stepx
            mult_cell      #(.N(N), .M(M))
            u_mult_step
            (
              .clk              (clk),
              .rstn             (rstn),
              .en               (rdy_t[i-1]),
              .mult1            (mult1_t[i-1]),
              .mult2            (mult2_t[i-1]),
              //上一次累加結(jié)果作為下一次累加輸入
              .mult1_acci       (mult1_acc_t[i-1]),
              //output
              .mult1_acco       (mult1_acc_t[i]),                                      
              .mult1_o          (mult1_t[i]),  //被乘數(shù)移位狀態(tài)傳遞
              .mult2_shift      (mult2_t[i]),  //乘數(shù)移位狀態(tài)傳遞
              .rdy              (rdy_t[i]) );
        end
    endgenerate

    assign res_rdy       = rdy_t[M-1];
    assign res           = mult1_acc_t[M-1];

endmodule

testbench

將下述仿真描述添加到非流水乘法器設(shè)計(jì)例子的 testbench 中,即可得到流水式乘法運(yùn)算的仿真結(jié)果。

2 路數(shù)據(jù)為不間斷串行輸入,且?guī)в凶孕r?yàn)?zāi)K,可自動(dòng)判斷乘法運(yùn)算結(jié)果的正確性。

    reg          data_rdy ;
    reg [N-1:0]  mult1 ;
    reg [M-1:0]  mult2 ;
    wire                 res_rdy ;
    wire [N+M-1:0]       res ;

    //driver
    initial begin
        #55 ;
        @(negedge clk ) ;
        data_rdy  = 1'b1 ;
        mult1  = 25;      mult2      = 5;
        #10 ;      mult1  = 16;      mult2      = 10;
        #10 ;      mult1  = 10;      mult2      = 4;
        #10 ;      mult1  = 15;      mult2      = 7;
        mult2      = 7;   repeat(32)    #10   mult1   = mult1 + 1 ;
        mult2      = 1;   repeat(32)    #10   mult1   = mult1 + 1 ;
        mult2      = 15;  repeat(32)    #10   mult1   = mult1 + 1 ;
        mult2      = 3;   repeat(32)    #10   mult1   = mult1 + 1 ;
        mult2      = 11;  repeat(32)    #10   mult1   = mult1 + 1 ;
        mult2      = 4;   repeat(32)    #10   mult1   = mult1 + 1 ;
        mult2      = 9;   repeat(32)    #10   mult1   = mult1 + 1 ;
    end

    //對(duì)輸入數(shù)據(jù)進(jìn)行移位,方便后續(xù)校驗(yàn)
    reg  [N-1:0]   mult1_ref [M-1:0];
    reg  [M-1:0]   mult2_ref [M-1:0];
    always @(posedge clk) begin
        mult1_ref[0] <= mult1 ;
        mult2_ref[0] <= mult2 ;
    end

    genvar         i ;
    generate
        for(i=1; i<=M-1; i=i+1) begin
            always @(posedge clk) begin
            mult1_ref[i] <= mult1_ref[i-1];
            mult2_ref[i] <= mult2_ref[i-1];
            end
        end
    endgenerate
   
    //自校驗(yàn)
    reg  error_flag ;
    always @(posedge clk) begin
        # 1 ;
        if (mult1_ref[M-1] * mult2_ref[M-1] != res && res_rdy) begin
            error_flag <= 1'b1 ;
        end
        else begin
            error_flag <= 1'b0 ;
        end
    end

    //module instantiation
    mult_man  #(.N(N), .M(M))
     u_mult
     (
      .clk              (clk),
      .rstn             (rstn),
      .data_rdy         (data_rdy),
      .mult1            (mult1),
      .mult2            (mult2),
      .res_rdy          (res_rdy),
      .res              (res));

仿真結(jié)果

前幾十個(gè)時(shí)鐘周期的仿真結(jié)果如下。

由圖可知,仿真結(jié)果判斷信號(hào) ?error_flag ?一直為 0,表示乘法設(shè)計(jì)正確。

數(shù)據(jù)在時(shí)鐘驅(qū)動(dòng)下不斷串行輸入,乘法輸出結(jié)果延遲了 4 個(gè)時(shí)鐘周期后,也源源不斷的在每個(gè)時(shí)鐘下無(wú)延時(shí)輸出,完成了流水線式的工作。


相對(duì)于一般不采用流水線的乘法器,乘法計(jì)算效率有了很大的改善。

但是,流水線式乘法器使用的寄存器資源也大約是之前不采用流水線式的 4 倍。

所以,一個(gè)數(shù)字設(shè)計(jì),是否采用流水線設(shè)計(jì),需要從資源和效率兩方面進(jìn)行權(quán)衡。

點(diǎn)擊這里下載源碼


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)