抄作業_verilog_實現64bits乘法器-3.9


版權聲明:本文為CSDN博主「掌閱讀書」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_30767945/article/details/112576775

抄一個Verilog大作業。

題目描述:用VerilogHDL設計實現64bit二進制整數乘法器

基本功能:

1.底層乘法器使用16*16\8*8\8*32\8*16小位寬乘法器來實現,底層乘法器可以使用FPGA內部IP實現;

2.基於modelsim仿真軟件對電路進行功能驗證;

3.基於Quartus平台對代碼進行綜合及綜合后仿真,芯片型號不限;

4.電路綜合后的工作頻率不低於50MHz。

第 1 章  電路的設計思想

1.1 不作優化的乘法器結構

對於一個乘法來說,它的計算步驟是用乘數的每一位以從低到高的順序與被乘數相乘再錯位相加。那么,如果把64位因數與64位因數相乘的結果展開以后會發現,錯位相加的那些加數是可以用16×16位乘法器或者8×64位乘法器的錯位相加的加數來湊一起的。我可以通過把四個3×3位乘法器的運算結果進行移位和相加以后,可以實現一個6×6位乘法器的運算結果。在上圖中,因為乘法器是要調用3×3位乘法器,所以需要把兩個因數分別切割成兩個3位來調用乘法器。所以需要2×2=4個乘法器。根據題目所給出的允許調用的乘法器里,為了使調用的乘法器個數最小,我的子乘法器選擇的范圍將聚焦在16×16位乘法器和8×32位乘法器。不論調用這里面那個乘法器,最終都是需要16個乘法器的。16個乘法器並行運算同時送出數據。

 

 


若調用16位乘法器設計電路,無優化的情況下需要調用16個乘法器,8×32位乘法器同理。此外,64位電路的設計的移位設計有着極強的規律,在電路的設計中,這種規律性會體現在電路結構的相似性。
調用8×32位乘法器的設計的分析思路與方法與上文類似。

1.2   乘法器結構的折疊優化

不論采用8×32位還是16位的子乘法器來設計64位乘法器,如果用十六個乘法器並行運算,然后再對結果進行移位和相加。為了保證並行計算的延續性在電路的右端還需要放置至少16個寄存器以及15個加法器。這些模塊湊一起將會導致電路的規模巨大。但是,在並形64位乘法器的設計里,電路的相乘以及移位的步驟是一樣的,在設計出來的電路中這些16個乘法器以及后面的移位的單元的結構都是一致的。因此我們可以對這一部分采用時分復用的方式進行優化。這種反復使用某個結構(部件)的過程也被稱作折疊。[2]折疊可以使得相同的N個結構優化到一個結構,代價是本來一個時鍾周期可以處理的過程需要在若干個(N個左右)時鍾周期完成。因此電路的吞吐率會下降到以下。

由於本次作業要求所設計的乘法器的面積與性能等指標的均衡,采用結構折疊可中和16個16位乘法所造成的很好的性能與很差的面積之間的矛盾。

調用8×32位乘法器的電路的設計與優化與上文雷同。

1.3  64位乘法器的多種設計思路

1.3.1  16個子乘法器的全並行思路

以調用16位子乘法器的設計為例。根據1.1節的結論,16個16位子乘法器先並行計算,然后按照(1.3)式來分配每一個乘法器后邊的128位移位寄存器,再把128位移位寄存器移位后存貯的數值加起來輸出。

為了減少加法器的延時,可把加法器改為樹形結構,即采用四級8421的加法器的結構。

其次,再乘法器設計里的加法器模塊,因為位寬至少也是16位的,所以加法器可采用進位旁路加法器來減少管子的數量。無論怎么,加法器電路的管子數量是隨着位寬的增加而增加的。如果寄存器直接采用128位,則加法器的位寬也應該是128位的。所以減小加法器的位寬可以從先減少寄存器的位寬再減小加法器的位寬入手。比如,第一級加法器的輸入采用32位寄存器,使得有移位要求的數據可以移位。第二級加法器的輸入增加4個64寄存器,第三級和第四級一共增加3個128寄存器。加法器的位寬四級下來也會減小。這樣優化下來,寄存器的總面積和加法器的總面積都會減小。

如1.1節所示,這種設計思路即使優化后仍然存在性能顯著與面積冗余的矛盾。因此,在設計中會繼續采用1.2節的思路對其優化。

1.3.2  1個子乘法器的全串行思路

現在,考慮采用1.2節的優化方法下最極端的思路,即僅采用一個子乘法器進行設計。電路框圖如下:

 

 

 

如上圖,將兩個64位的因數分別切成16位數據,由時鍾驅動選擇器在不同時刻選擇不同的數據組合並在乘法器中相乘。然后把相乘的結果移位以后並與寄存器里的數據相加,然后當累加次數夠了以后把累加結果作為64位乘法器的結果輸出。關於電路還有如下幾點說明。

時鍾驅動選擇器選擇數據是通過計數器實現的,比如時鍾要驅動一個四選一的多路選擇器,那么需要在時鍾和選擇器之間加上一個四進制計數器,然后時鍾每跳一下計數器加一位。當計數器的計數結果連接到選擇器的使能端上時,可實現乘法器的乘數隨着時鍾的跳變而轉換。

選擇器MUX1在跳轉時,要固定選擇器MUX2里一個數保持不變,直至MUX1里所有的數都循環結束。因此MUX1的時鍾信號的頻率應該是MUX2頻率的4倍,同樣道理,因為最終的結果需要移位累加4次才能出結果,所以最后一個寄存器的時鍾頻率應當是MUX2頻率的0.25。

如果采用多個時鍾信號對於這種小模塊來說是很浪費的,那么可以考慮給控制選擇使能端的兩個計數器添加進位輸出並級聯。這樣可以使用一個全局時鍾。

為了提高數據的吞吐率,電路中多加了幾級流水線。

采用8×32位寄存器設計與上述思路雷同,無非是選擇器分別使用二選一選擇器與四選一選擇器。時鍾的進位也不一樣。

1.4  最終的乘法器結構設計方案

1.4.1     X個(1

在1.3節我分別列出來了兩種極端情況下的電路設計情況,為了協調電路面積、性能、功耗三者的關系,我采用兩種措施來保證電路的設計。首先是采用串並混合的電路,根據作者本人的粗略估計,並行的乘法器為四個16位乘法器的時候電路的面積和性能會有一個均衡。其次是為保證在前者情況下功耗的最優,電路設計的順序結構應為先並行再串行。如果采用先串行再並行,那么在串行的時候電路里有不產生結果的時鍾周期,而這種時鍾周期在並行以后依然不產生結果,會造成時鍾的浪費。對於有把握的地方,可分割的組合邏輯模塊可在中間插入流水線分割組合邏輯電路來提高性能,但是這樣會降低芯片的響應速度。如果假設乘法器的應用場景為大串的乘法連續計算,那就盡量加上。最后要算好延時等時序問題,保證在正確的時刻把正確的積進行輸出 (由計數器控制)。

第 2 章  電路的實現

4個16×16位乘法器實現64位乘法器所組成的64位乘法器的電路示意圖如下:

其中,16×64位乘法器的設計參考1.1節的全並行乘法器設計;64×64位乘法器的設計調用16×64位的乘法器並參考1.2節的串行乘法器的設計。下面將分別給出16×64位乘法器和64×64位乘法器的設計圖並詳細說明。

2.1   64位乘法器流程圖

 

 

 

圖2.1  16×64位乘法器電路示意圖

 

 

 

圖2.2  64×64位乘法器電路示意圖

2.2   64位乘法器設計細節

2.2.1  端口的wire連接

為了規避語法問題帶來的編譯通不過,我將所有的module的端口都定義為wire類型。而調用的IP的乘法器的端口也是wire類型,這樣實例化這些模塊以后的連接就比較方便。

其中可能會存在需要在敏感事件列表里對端口進行賦值,這時候便定義reg類型的中間變量來參與賦值和運算,最終用assign語句將它和端口進行連接。

2.2.2  選擇器的時鍾控制

利用1.3.2節的思路,用時鍾驅動計數器工作,再用計數器的輸出作為選擇器的輸入,來選擇數據,從而實現時鍾驅動數據的跳變。

2.2.3  流水線插入

為了提高電路的工作頻率,我插入了一些流水線,諸如在兩級乘法器的移位累加過程中,多級求和過程中。結果的輸出過程等。這樣有利於把長路徑的組合邏輯電路進行分割,以減小組合邏輯的延遲,提高頻率。

流水線沒有全加,因為加的多了面積也會增大,而且,結果的響應時間也會拉長。

2.2.4  移位累加過程[3]

按照傳統的思維,錯位相加應該是先把低位來的數據放在寄存器低位,然后寄存器的數據左移,再和次低位來的數據相加,依次循環到最高位的結果。我在代碼里的移位累加過程剛剛反過來。即先把低位來的數據放在最高位,然后寄存器數據右移后與次高位來的數據相加,最終的效果差不多。

最終的操作步驟是,先把寄存器清零,然后寄存器右移16位,和低位來的數據放最高位以后的數據相加並放回寄存器,然后一直重復右移,累加,放值的過程

移位累加過程如果在移位和累加過程中間加一層流水線的話延時可能會顯著降低,但是考慮到這里的寄存器數量太多,於是放棄。

2.2.5  移位累加過程的控制

每一個計算周期每一步都會通過計數器進行標明。所以可以根據計數結果來控制移位累加的過程。

移位累加的過程需要注意到兩個問題,一個是一個計算周期開始的時候,累加的寄存器內數據需要歸零並開始計算。如果不歸零那寄存器會累加上之前計算的結果。所以我們需要推算出第一個16×64位乘法的結果在哪個周期首先產生,那在這個周期之后的第一個上升沿就會發生程序的第一次移位累加,往前推導一下,在這個周期寄存器里存放的恰好是上一個計算周期的結果,所以,在下一個上升沿還需要把寄存器里的數據賦出去。

事實上,對於電路時序的確認我的推算還是有一些誤差,最終在跑仿真的時候檢查發現有錯位便再進行這部分的修改,即對if語句的參數的修改。

2.3     電路的Verilog HDL關鍵代碼及解釋

2.3.1  16×64代碼及解釋

//16×64子乘法器模塊module mul1664 (clk,reset,a, b, result); input clk; input [15:0] a;//a為16位 input [63:0] b;//b為64位 output [79:0] result; reg [31:0] temp_result1; reg [31:0] temp_result2; reg [31:0] temp_result3; reg [31:0] temp_result4; reg [47:0] temp_sum1; reg [47:0] temp_sum2; reg [79:0] temp_result; //非阻塞賦值,使得程序自帶流水線 initial always@(posedge clk) if(!reset) begin temp_sum1<=0; temp_sum2<=0; temp_result<=0; end else begin //第一個時鍾上升沿:調用四個乘法器同時運算 mul64 submul1(.dataa(a),.datab(b[15:0]),.result(temp_result1) ); mul64 submul2(.dataa(a),.datab(b[31:16]),.result(temp_result2) ); mul64 submul3(.dataa(a),.datab(b[47:32]),.result(temp_result3) ); mul64 submul4(.dataa(a),.datab(b[63:48]),.result(temp_result4) ); //第二個時鍾上升沿:第一層加法同時運算,錯位相加 temp_sum1<={temp_result2,16'b0}+{16'b0+temp_result1}; temp_sum2<={temp_result4,16'b0}+{16'b0+temp_result3}; //第三個時鍾上升沿:第二層加法同時運算,錯位相加 temp_result<={temp_sum1,32'b0}+{temp_sum2,32'b0}; end assign result=temp_result;endmodule//16×16bit乘法器//由IP生成,即mul64
2.3.1  64×64代碼及解釋

//64×64位乘法器module multiply64(clk,reset,a,b,result); //輸入信號:時鍾信號和復位信號 //時鍾控制計數器、乘法器的流水線工作以及一個計算周期內最終信號的移位累加過程的進行 //復位信號控制計數器、乘法器的復位 input clk; input reset; //輸入信號:兩個64位的乘數 input [63:0] a; input [63:0] b; //輸出信號:64位乘法器的運算結果 output [127:0] result; //enable使能端,兩個作用: //第一,控制4選一多路選擇器選擇一個計算周期每個時刻下正確的復用信號 //第二,控制實現最終移位累加的128位寄存器temp1_result實現正確的復位,賦初值以及把正確結果送到寄存器temp_result wire [1:0] enable; //選擇器選擇的結果,作為16×64位乘法器的乘數 wire [15:0] temp16; //作為復用的16×64位乘法器的運算結果 wire [79:0] temp1664; //實現復用乘法器結果temp1664的移位和累加 reg [127:0] temp1_result; //存放一個計算周期結束后的64位乘法器的計算結果 reg [127:0] temp_result; //用時鍾驅動計數器計數,每四個周期是一個循環,無進位 //因為乘法器是復用了四次,所以只要選擇合適的時序以及恰當的時間點,那么完全可以實現一個周期四個時鍾,不會再多 //如果不對這一塊作優化而是無腦堆流水線的話則需要五、六個周期甚至更多 count sel(.clk(clk), .reset(reset), .control_cnt(enable) ); //用選擇器選擇是哪16位參與運算 //我們調用16×64位乘法器,當64位的數確定以后,我們需要把另一個64位的數每隔16位進行切割 //然后通過由時鍾所驅動的計數器來控制某一個時刻下,我選擇哪16位參與我們所復用的16×64位乘法器的運算 //使能端由計數器的計數結果進行控制 mux4in1 choice(.a(a[15:0]), .b(a[31:16]), .c(a[47:32]), .d(a[63:48]), .en(enable), .mux_out(temp16) ); //調用16×64位乘法器來進行運算 mul1664 submul1664( .clk(clk), .reset(reset), .a(temp16), .b(b), .result(temp1664) ); always@(posedge clk) //把”計數器為2“設置為計算周期的開始標志位 //通過對時序的推算可知,在全部時鍾周期里,16×64位計數器第一次出結果的時候,計數器為1 //這樣,如果再經過三次時鍾周期,那么便可以完成一次16×64位乘法器的復用,即四次以后應該出移位累加寄存器temp1_result的結果 //此時,由於四進制計數器的緣故,此時的計數結果仍然是1 //但是,從16×64位乘法器到temp1_result之間有一層流水,所以,temp1_result的數據有一個延時 //因此選擇在enable==2'b10時進行temp1_result初始化與result賦值 if(enable==2'b10) begin //每一個計算周期開始以后,賦初值 temp1_result<={temp1664,48'b0}; //當一個運算周期開始以后,由寄存器輸出上一個運算周期結果 temp_result<=temp1_result; end //完成移位相加的過程,並使寄存器鎖存數據 //移位累加過程: //每個計算周期內,乘法器復用時采用的乘數分別是一個64位乘數的低16位到高16位 //那么就需要把這四次的結果按照時間先后順序從低位到高位依次左移16位並相加 //為了串行運算的電路設計,我采用設計一般乘法器的一個電路設計 //便是每次的結果都是以高位存放,然后和右移16位的累加寄存器的結果相加並把結果存放在累加寄存器temp1_result else begin //其他時刻寄存器內數值保持不變 temp_result<=temp_result; //對乘法器的計算結果進行16位移位並與此時16×64位乘法器的運算結果累加 temp1_result<={16'b0,temp1_result[127:16]}+{temp1664,48'b0}; end //把寄存器的計算結果連接到輸出 assign result=temp_result; endmodule//四選一選擇器//控制選擇一個計算周期每個時刻下正確的復用信號module mux4in1(a,b,c,d,en,mux_out); //輸入信號:四個備選信號 input[15:0] a; input[15:0] b; input[15:0] c; input[15:0] d; //輸入信號:使能端 input[1:0] en; //輸出信號:選擇器選擇的結果 output[15:0] mux_out; //輸出信號的中間變量 reg[15:0] temp_out; //組合邏輯電路 always@(a or b or c or d or en) //case語句用來做數據選擇 case(en) 2'b00:temp_out =a; 2'b01:temp_out =b; 2'b10:temp_out =c; 2'b11:temp_out =d; //防止latch出現 default:temp_out=15'b0; endcase //輸出信號賦值 assign mux_out=temp_out;endmodule//控制要用的計數器模塊,每四個時鍾周期進一位,但是無進位信號//也是每四個時鍾周期完成一次64×64位乘法器的運算 //計數器得到的計數結果最主要的目的在於:在每一個計算周期內標注好了我每個時鍾周期  //這樣我就可以知道某個周期電路應該做什么,並通過使計數結果作使能端來控制電路的行為module count(clk,reset,control_cnt); //輸入信號:時鍾信號和復位信號時鍾和復位信號 input clk; input reset; //輸出信號:計數器的計數結果 output [1:0] control_cnt; //存放計數結果的寄存器 //為了我在宏觀module里線路的連接便捷,我基本都把input信號以及output信號定義成了默認的wire類型信號 //但是,在這些module的內部,敏感事件列表里往往會對輸出信號進行賦值,這里我便用一個臨時的reg類型信號來做輸出的臨時信號 //注:本文件中所有命名帶有“temp”字符串的變量均為臨時變量,起規避語法錯誤(syntax Error)的作用或者做其它中間變量 reg [1:0] temp_cnt; //每遇到一次時鍾上升沿計數器加1 always@(posedge clk or negedge reset) //復位計數結果清零 if(!reset) temp_cnt<=2'b00; //遇三計數結果清零重新計數 else if(temp_cnt==2'b11) temp_cnt<=2'b00; //計數 else temp_cnt<=temp_cnt+2'b01; //輸出信號賦值 assign control_cnt=temp_cnt;endmodule
————————————————
版權聲明:本文為CSDN博主「掌閱讀書」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_30767945/article/details/112576775


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM