01 設計思路 ? 二進制的乘法運算與十進制的乘法運算相似,如下圖所示,二進制數(shù)據(jù)6’b110010乘以二進制數(shù)據(jù)4’b1011,得到乘積結(jié)果10’b1000100110。
圖1 二進制乘法運算 仔細觀察上圖發(fā)現(xiàn),乘數(shù)最低位為1(上圖紫色數(shù)據(jù)位),則得到紫色數(shù)據(jù),乘數(shù)第1位為1,將被乘數(shù)左移1位,得到橙色數(shù)據(jù),然后乘數(shù)的第2位是0,0乘以被乘數(shù)為0,則舍棄。乘數(shù)的第3位為1,則將被乘數(shù)左移3位,得到紅色數(shù)據(jù)。然后將紫色、橙色、紅色數(shù)據(jù)相加,得到乘積。 這就是二進制乘法運算思路,乘法的運算時間與乘數(shù)的位寬有關。乘數(shù)為1時需要左移的位數(shù)與數(shù)據(jù)位的權重其實有關,但是FPGA實現(xiàn)這樣的運算并不算特別簡單,還能不能簡化? 當乘數(shù)或者被乘數(shù)為0時,直接輸出0即可,不需要運算。 當乘數(shù)和被乘數(shù)均不等于0時,乘積的初始值為0,每個時鐘周期把乘數(shù)右移一位,被乘數(shù)左移一位,如果乘數(shù)最低位為1,則乘積等于乘積加上此時被乘數(shù)的值,當乘數(shù)為1時,計算完成,輸出乘積的運算結(jié)果。 計算流程如下圖所示,其實就是將圖1的運算拆分,每次只需要判斷乘數(shù)的最低位是否為1,從而確定乘積是否需要加上被乘數(shù),乘數(shù)每右移一次,被乘數(shù)就必須左移一次,這樣能保證乘積不變。當乘數(shù)變?yōu)?時,移位結(jié)束,此時乘數(shù)最低位為1,被乘數(shù)加上乘積后作為運算結(jié)果,完成運算。
圖2 簡化的移位相加運算
02 代碼設計 ? 由此,就可以編寫FPGA代碼了,為了模塊通用,位寬全部進行參數(shù)化設計,增加開始計算信號和模塊忙閑指示信號,以及乘積計算完成的有效指示信號。 端口信號如下表所示:
表1 端口信號列表
?
信號 | I/O | 位寬 | 含義 |
clk | I | 1 | 系統(tǒng)時鐘。 |
rst_n | I | 1 | 系統(tǒng)復位,低電平有效。 |
start | I | 1 | 開始運算,高電平有效。 |
multiplicand | I | MULT_D | 被乘數(shù)。 |
multiplier | I | MULT_R | 乘數(shù)。 |
product | O |
MULT_D + MULT_R |
乘積。 |
product_vld | O | 1 | 乘積有效指示信號,高電平有效 |
rdy | O | 1 | 模塊空閑指示信號,高電平有效。 |
?
當開始計算信號有效且乘數(shù)與被乘數(shù)均不等于0且模塊不處于運算狀態(tài)時,把開始計算信號start_f拉高,運算狀態(tài)標志信號flag初始值為0,當檢測到開始運算start_f有效時拉高,當乘數(shù)為1時結(jié)束運算,flag信號拉低,對應代碼如下所示:
?
?
//開始計算信號有效且乘數(shù)和被乘數(shù)均不等于0; assign start_f = (~flag) && (start && (multiplicand != 0) && (multiplier != 0)); //運算標志信號, always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; flag <= 1'b0; end else if(start_f)begin//開始運算時拉高 flag <= 1'b1; end else if(multiplier_r == 1)begin//運算結(jié)束時拉低; flag <= 1'b0; end end
?
?
然后就是對乘數(shù)和被乘數(shù)信號的處理,如下所示。初始值均為0,當開始運算時,將輸入的乘數(shù)和被乘數(shù)保存到相應寄存器中,如果flag信號有效,則每個時鐘周期把乘數(shù)右移1位,把被乘數(shù)左移1位。
?
?
always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; multiplicand_r <= {{MULT_D + MULT_R}{1'b0}}; multiplier_r <= {{MULT_R}{1'b0}}; end else if(start_f)begin//當計算開始時; multiplicand_r <= multiplicand;//將被乘數(shù)加載到被乘數(shù)寄存器中。 multiplier_r <= multiplier;//將乘數(shù)加載到乘積寄存器中。 end else if(flag)begin//正常計算標志信號有效時,被乘數(shù)左移一位,乘數(shù)右移一位。 multiplicand_r <= multiplicand_r << 1; multiplier_r <= multiplier_r >> 1; end end
?
?
之后就是乘積的運算,初始值為0,當開始信號有效時,不管乘數(shù)和被乘數(shù)的狀態(tài)是什么,將乘積寄存器設置為0。在之后的運算中,如果flag有效并且乘數(shù)最低位為1,則把乘積寄存器的值與被乘數(shù)寄存器的值相加,得到乘積寄存器數(shù)據(jù)。
?
?
//計算乘法運算結(jié)果,開始信號有效時,將乘積清零。 //當乘數(shù)寄存器最低位為1時,加上此時被乘數(shù)的值。 always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; product_r <= {{MULT_D + MULT_R}{1'b0}}; end else if(start)//當乘數(shù)或者被乘數(shù)為0時,乘積輸出0. product_r <= {{MULT_D + MULT_R}{1'b0}}; else if(flag && multiplier_r[0])begin//如果乘積的最低位為1,則把乘積的高位數(shù)據(jù)與被乘數(shù)相加。 product_r <= product_r + multiplicand_r; end end
?
?
最后就是乘積運算的輸出,如果開始信號有效時,乘數(shù)和被乘數(shù)其中一個為0,則乘積輸出0,拉高乘積有效指示信號。如果在計算乘積的過程中(flag為高電平)且乘數(shù)等于1,則表示計算完成,把乘積寄存器值加上此時被乘數(shù)的值作為乘積輸出,并且把乘積有效指示信號拉高一個時鐘周期。乘積有效指示信號在其余時間均為0。
?
?
//輸出乘積和乘積有效指示信號; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; product <= {{MULT_D + MULT_R}{1'b0}}; product_vld <= 1'b0; end else if((~flag) && (start && ((multiplicand == 0) || (multiplier == 0))))begin product <= {{MULT_D + MULT_R}{1'b0}};//如果開始計算時,乘數(shù)或者被乘數(shù)為0,則直接輸出0; product_vld <= 1'b1; end else if(flag && (multiplier_r == 1))begin//計算完成時,把計算結(jié)果輸出,且乘積有效指示信號拉高; product <= product_r + multiplicand_r; product_vld <= 1'b1; end else begin//其余時間把有效指示信號拉低; product_vld <= 1'b0; end end
?
?
最后就是模塊忙閑指示信號,當開始信號有效或者模塊處于計算狀態(tài)時拉低,其余時間拉高,上游模塊檢測到該信號后就可以拉高start信號,開始下一次運算。注意該信號只能使用組合邏輯電路生成,并且上游只能通過時序電路檢測該信號狀態(tài)。
?
?
//生成模塊忙閑指示信號; always@(*)begin//當開始信號有效或者標志信號有效時,模塊處于工作狀態(tài); if(start || flag) rdy = 1'b0; else//否則模塊處于空閑狀態(tài); rdy = 1'b1; end
?
?
代碼就這么多,相對比較簡單,參考代碼如下:
?
?
module mult #( parameter MULT_D = 8 ,//被乘數(shù)位寬; parameter MULT_R = 4 //乘數(shù)位寬; )( input clk ,//系統(tǒng)時鐘信號; input rst_n ,//系統(tǒng)復位信號,低電平有效; input start ,//開始運算信號,高電平有效; input [MULT_D - 1 : 0] multiplicand ,//被乘數(shù); input [MULT_R - 1 : 0] multiplier ,//乘數(shù); output reg [MULT_D + MULT_R - 1 : 0] product ,//乘積輸出; output reg product_vld ,//乘積有效指示信號,高電平有效; output reg rdy //模塊忙閑指示信號,高電平表示空閑; ); reg flag ; reg [MULT_D - 1 : 0] multiplier_r ;//乘數(shù)的寄存器 reg [MULT_D + MULT_R - 1 : 0] multiplicand_r ;//被乘數(shù)的寄存器。 reg [MULT_D + MULT_R - 1 : 0] product_r ;//乘積寄存器; wire start_f ; //開始計算信號有效且乘數(shù)和被乘數(shù)均不等于0; assign start_f = (~flag) && (start && (multiplicand != 0) && (multiplier != 0)); //運算標志信號, always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; flag <= 1'b0; end else if(start_f)begin//開始運算時拉高 flag <= 1'b1; end else if(multiplier_r == 1)begin//運算結(jié)束時拉低; flag <= 1'b0; end end always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; multiplicand_r <= {{MULT_D + MULT_R}{1'b0}}; multiplier_r <= {{MULT_R}{1'b0}}; end else if(start_f)begin//當計算開始時; multiplicand_r <= multiplicand;//將被乘數(shù)加載到被乘數(shù)寄存器中。 multiplier_r <= multiplier;//將乘數(shù)加載到乘積寄存器中。 end else if(flag)begin//正常計算標志信號有效時,被乘數(shù)左移一位,乘數(shù)右移一位。 multiplicand_r <= multiplicand_r << 1; multiplier_r <= multiplier_r >> 1; end end //計算乘法運算結(jié)果,開始信號有效時,將乘積清零。 //當乘數(shù)寄存器最低位為1時,加上此時被乘數(shù)的值。 always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; product_r <= {{MULT_D + MULT_R}{1'b0}}; end else if(start)//當乘數(shù)或者被乘數(shù)為0時,乘積輸出0. product_r <= {{MULT_D + MULT_R}{1'b0}}; else if(flag && multiplier_r[0])begin//如果乘積的最低位為1,則把乘積的高位數(shù)據(jù)與被乘數(shù)相加。 product_r <= product_r + multiplicand_r; end end //輸出乘積和乘積有效指示信號; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; product <= {{MULT_D + MULT_R}{1'b0}}; product_vld <= 1'b0; end else if((~flag) && (start && ((multiplicand == 0) || (multiplier == 0))))begin product <= {{MULT_D + MULT_R}{1'b0}};//如果開始計算時,乘數(shù)或者被乘數(shù)為0,則直接輸出0; product_vld <= 1'b1; end else if(flag && (multiplier_r == 1))begin//計算完成時,把計算結(jié)果輸出,且乘積有效指示信號拉高; product <= product_r + multiplicand_r; product_vld <= 1'b1; end else begin//其余時間把有效指示信號拉低; product_vld <= 1'b0; end end //生成模塊忙閑指示信號; always@(*)begin//當開始信號有效或者標志信號有效時,模塊處于工作狀態(tài); if(start || flag) rdy = 1'b0; else//否則模塊處于空閑狀態(tài); rdy = 1'b1; end endmodule
?
?
03 模塊仿真
對應的TestBench如下所示:
?
?
`timescale 1 ns/1 ns module test(); localparam CYCLE = 10 ;//系統(tǒng)時鐘周期,單位ns,默認10ns; localparam RST_TIME = 10 ;//系統(tǒng)復位持續(xù)時間,默認10個系統(tǒng)時鐘周期; localparam MULT_D = 8 ;//被乘數(shù)位寬; localparam MULT_R = 4 ;//乘數(shù)位寬; reg clk ;//系統(tǒng)時鐘,默認100MHz; reg rst_n ;//系統(tǒng)復位,默認低電平有效; reg start ;//開始運算信號,高電平有效; reg [MULT_D - 1 : 0] multiplicand;//被乘數(shù); reg [MULT_R - 1 : 0] multiplier ;//乘數(shù); wire [MULT_D + MULT_R - 1 : 0] product ;//乘積輸出; wire product_vld ;//乘積有效指示信號,高電平有效; wire rdy ;//模塊忙閑指示信號,高電平表示空閑; //例化需要仿真的模塊; mult #( .MULT_D ( MULT_D ),//被乘數(shù)位寬; .MULT_R ( MULT_R ) //乘數(shù)位寬; ) u_mult ( .clk ( clk ),//系統(tǒng)時鐘,默認100MHz; .rst_n ( rst_n ),//系統(tǒng)復位,默認低電平有效; .start ( start ),//開始運算信號,高電平有效; .multiplicand ( multiplicand ),//被乘數(shù); .multiplier ( multiplier ),//乘數(shù); .product ( product ),//乘積輸出; .product_vld ( product_vld ),//乘積有效指示信號,高電平有效; .rdy ( rdy ) //模塊忙閑指示信號,高電平表示空閑; ); //生成周期為CYCLE數(shù)值的系統(tǒng)時鐘; initial begin clk = 0; forever #(CYCLE/2) clk = ~clk; end //生成復位信號; initial begin rst_n = 1;start = 0;multiplicand = 0; multiplier = 0; #2; rst_n = 0;//開始時復位10個時鐘; #(RST_TIME*CYCLE); rst_n = 1; #(5*CYCLE); multiplicand = 4; multiplier = 15; start = 1'b1; #(CYCLE); start = 1'b0; #(CYCLE); repeat(30)begin//產(chǎn)生30組隨機數(shù)據(jù)進行測試; @(posedge rdy); #(8*CYCLE); #1; multiplicand = {$random};//產(chǎn)生隨機數(shù)據(jù),作為被乘數(shù); multiplier = {$random};//產(chǎn)生隨機數(shù)據(jù),作為乘數(shù); start = 1'b1; #(CYCLE); start = 1'b0; end @(posedge rdy); #(8*CYCLE); $stop;//停止仿真; end endmodule簡要截取仿真的一段數(shù)據(jù)進行查看,如下所示,start信號有效時,乘數(shù)為13,被乘數(shù)為92。之后被相應的寄存器暫存,然后flag信號為高電平時,每個時鐘周期乘數(shù)寄存器右移一位,被乘數(shù)寄存器數(shù)據(jù)左移一位。 如果乘數(shù)最低位為1,則乘積寄存器的值就會與被乘數(shù)的值相加,得到新的乘積寄存器值,最后當乘數(shù)為1時,藍色的乘積信號就會把乘積寄存器的值460與被乘數(shù)的值736相加得到1196作為輸出,完成乘法運算。
?
?
圖3 仿真截圖 至此,該模塊的設計到此結(jié)束,該模塊的位寬全部進行了參數(shù)化處理,需要修改乘數(shù)和被乘數(shù)的位寬時,只需要修改位寬的參數(shù)即可,代碼不需要做任何修改。 該模塊在后續(xù)設計中可能作為子模塊出現(xiàn),因為這種靠移位和加法的運算,在面對較大位寬的乘法運算時,可以得到更高的時鐘頻率。 審核編輯:黃飛
?
評論