在設計FPGA時,大多數采用Verilog HDL或者VHDL語言進行設計(本文重點以verilog來做介紹)。設計的電路都是利用FPGA內部的LUT和觸發器等效出來的電路。
數字邏輯電路分為組合邏輯電路和時序邏輯電路。時序邏輯電路是由組合邏輯電路和時序邏輯器件構成(觸發器),即數字邏輯電路是由組合邏輯和時序邏輯器件構成。所以FPGA的最小單元往往是由LUT(等效為組合邏輯)和觸發器構成。
在進行FPGA設計時,應該采用組合邏輯設計還是時序邏輯?這個問題是很多初學者不可避免的一個問題。
設計兩個無符號的8bit數據相加的電路。
組合邏輯設計代碼:
module adder_8bit (
input wire [7:0] dataa, input wire [7:0] datab,
output wire [8:0] datas );
assign datas = dataa + datab;
endmodule |
對應的電路為:
時序邏輯對應代碼為:
module adder_8bit (
input wire clk, input wire rst_n,
input wire [7:0] dataa, input wire [7:0] datab,
output reg [8:0] datas );
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) datas <= 9'd0; else datas <= dataa + datab; end
endmodule |
對應的電路為:
可以思考一下,這個兩種設計方法都沒有任何錯誤。那么在設計時應該用哪一種呢?
在設計時,有沒有什么規定必須要用組合邏輯或者時序邏輯?例如:在verilog中,在always中被賦值了就必須是reg類型,assign賦值了就必須是wire類型。很遺憾的是,目前沒有任何的規定。
下面幾點筆者平時自己做設計的經驗,在這里分享一下:
一、 帶有反饋的必須用時序邏輯
何為帶有有反饋?即輸出結果拉回到輸入。
自加一計數器。
代碼為:
// assign cnt = cnt + 1'b1;
always @ (*) begin cnt = cnt + 1'b1; end |
對應的電路為
這種電路在工作時,就會出現無限反饋,不受任何控制,一般情況下,我們認為結果沒有任何意義。
和上面的情況類似的還有取反。
assign flag = ~flag; |
類似情況還有很多就不在一一列舉。
上述說的情況都是直接帶有反饋,下面說明間接反饋。
代碼為:
assign k = cnt * 2;
always @ (*) begin cnt = k + 1'b1; end |
從代碼上來看,沒有什么明確反饋,下面看實際對應的電路
從實際的電路上來看,一旦運行起來,還是會出現無限反饋,不受任何控制。
還有一種情況是帶有控制的反饋。
設計代碼為:
always @ (flag) begin if (flag == 1'b1) cnt = cnt + 1'b1; else cnt = cnt; end |
這個電路可以等效為
在flag等於1期間,此電路依然會無限制的反饋,無法確定在此期間進行了多少次反饋。
從代碼的角度理解是flag變化一次,加一次。可是對應於電路后,和預想的是不相同的。
說了這么多的這么多不對的情況,下面考慮正確的情況
設計代碼為:
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) cnt <= 4'd0; else cnt <= cnt + 1'b1; end |
在上述的電路中,clk每來一個上升沿,cnt的數值增加一。可以用作計時使用。
利用寄存器將反饋路徑切換即可。此時的反饋是可控制,並且此時的結果就有了意義。
其他的反饋中,加入寄存器即可。而加入寄存器后,就變為時序邏輯。
二、 根據時序對齊關系進行選擇
在很多的設計時,沒有反饋,那么應該如何選擇呢?
舉例說明:輸入一個八位的數據(idata),然后將此八位數據進行平方后,擴大2倍,作為輸出。要求輸出結果(result)時,將原數據同步輸出(odata),即數據和結果在時序上是對齊的。
設計代碼為:
assign odata = idata; assign result = 2 * (idata * idata); |
這種設計方法是可以的,因為都采用組合邏輯設計,odata和result都是和idata同步的,只有邏輯上的延遲,沒有任何時鍾的延遲。
另外一種設計代碼為:
assign odata = idata;
always @ (posedge clk, negedge rst_n) begin if (rst_n == 1'b0) odata <= 17'd0; else result <= 2 * (idata * idata); end |
這種設計方法為錯誤,odata的輸出是和idata同步的,而result的輸出將會比idata晚一拍,最終導致result要比odata晚一拍,此時結果為不同步,設計錯誤。
修改方案為:將result的寄存器去掉,修改為組合邏輯,那就是第一種設計方案。第二種為將odata也進行時序邏輯輸出,那么此時odata也將會比idata延遲一拍,最終結果為result和odata同步輸出。
三、 根據運行速度進行選擇
在數字邏輯電路中,中間某一部分為組合邏輯,兩側的輸入或者輸出也會對延遲或者輸入的數據速率有一定的要求
組合邏輯1越復雜延遲越大,而導致的結果就是clk的時鍾速率只能降低,進而導致設計結果失敗。
當組合邏輯1無法進行優化時,還想要達到自己想要的速度時,我們可以進行邏輯拆分,增加數據的輸出潛伏期,增加數據的運行速度。
將組合邏輯1的功能拆分為組合邏輯A和組合邏輯B,此時,輸入的數據得到結果雖然會多延遲一拍,但是數據的流速會變快。
那么這個和選用組合邏輯和時序邏輯有什么關系呢?
舉例說明:目前要設計模塊A,不涉及反饋,不涉及時序對齊等,可以采取組合邏輯設計也可以采用時序邏輯設計。
模塊A的輸出連接到模塊B,經過一些變換(組合邏輯N)連接到某個寄存器K上。如果模塊A采用組合邏輯,那么模塊A的組合邏輯和模塊B到達寄存器K之前的組合邏輯N會合並到一起。那么此時組合邏輯的延遲就會變得很大,導致整體設計的時鍾速率上不去。
當運行速率比較快時,建議對於復雜的組合邏輯進行拆分,有利於時序分析的通過。
在上述的三個規則中,第一個和第二個用的是最多的,第三個在設計時,有時不一定能夠注意到,當出現時序違例時,知道拆分能夠解決問題就可以。
設計者:郝旭帥 QQ:746833924 QQ交流群: 173560979