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