本文是對實驗課上講解的“面向硬體電路的設計思維”的總結,結合數字邏輯課本,進行提煉和整理。
主要來源是課件與本人整理,部分參考了網路大佬的部落格。
本文主要介紹不同於之前軟體設計思維的硬體設計思維,從非阻塞賦值、並行、面積速度轉換、同步電路設計原則、模組劃分設計、if-case對比等方面進行整理。
內容太多,我整理了好幾天,在浩如煙海的網路前有點無力,想想是自己的實踐不夠,有一些問題沒有親身體驗;也不能一蹴而就,得久久為功。所以這篇文章就當作一個Verilog學習與FPGA設計的總述性文章,後續繼續學習我會加深對這些知識的理解。
00 阻塞賦值和非阻塞賦值
概念
回憶一下課本上的相關內容。
-
阻塞賦值:
-
"="
-
Verilog編譯器按照這些語句在always塊中的先後順序依次執行。
-
如果一個變數通過阻塞賦值語句賦值,則這個新賦的值會在這個block中的後續語句中使用。
-
相當於序列
-
-
非阻塞賦值:
-
"<="
-
always塊中所有非阻塞賦值的語句在求值時所用的值是最初進入always時各個變數已經具有的值。
-
換一個角度講,"<="左側的被賦值變數,只在always結束時統一被更新。
-
相當於並行
-
時序電路例項
阻塞賦值
先來看一看這段程式碼:
1 module example5_5 (x1, x2, x3, Clock, f, g); 2 input x1, x2, x3, Clock; 3 output reg f, g; 4 always @(posedge Clock) 5 begin 6 f = x1 & x2; 7 g=f | x3; 8 end 9 endmodule
可以看到關鍵點是g的表示式,由於是阻塞賦值,所以相當於:
g=(x1 & x2) | x3;
綜合出的電路如圖:
非阻塞賦值
1 module example5_6 (x1, x2, x3, Clock, f, g); 2 input x1, x2, x3, Clock; 3 output reg f, g; 4 always @(posedge Clock) 5 begin 6 f <= x1 & x2; 7 g <= f | x3; 8 end 9 endmodule
這個區別之處就在於這個g,是x3與前一個 f 進行"|"運算。
思考
將阻塞賦值的示例程式碼中的兩個執行語句互換位置,會發生什麼情況?
可以料見影響比較大。
組合邏輯例項
相反的,如果我們要實現 f = a1a0 + a2a1這樣一個函式;
阻塞賦值
1 always @(A) 2 begin 3 f = A[1] & A[0]; 4 f=f | (A[2] & A[1]); 5 end
非阻塞賦值
1 always @(A) 2 begin 3 f <= A[1] & A[0]; 4 f <= f | (A[2] & A[1]); 5 end
可見這段程式碼有兩個特徵;
-
非阻塞賦值的結果在always結束後才可以看到
-
多次賦值時,後覆蓋前。
這兩個特徵使得第二個f語句出現問題,因為第二個語句
f <= f | (A[2] & A[1]);
右側的f的值是不可見的。
總結
-
對組合邏輯建模採用阻塞賦值
-
對時序邏輯建模採用非阻塞賦值
-
用多個always塊分別對組合和時序邏輯建模
-
儘量不要在一個always塊裡面混合使用阻塞賦值和非阻塞賦值。如果在同一個塊即為組合邏輯又為時序邏輯,應使用“非阻塞賦值”
01 程式是並行執行的
這裡給的例子沒怎麼看懂。自己查了一下。
先說結論:
-
各個always塊是並行執行的,
-
always塊和initial塊之間是並行執行的,
-
begin-end塊內是順序執行的,
-
但是非阻塞賦值(<=)是並行執行的,阻塞賦值(=)是順序執行的,這條優先。且硬體思想的集中體現就是前面提到過的非阻塞賦值帶來的並行執行語句。
再回去看例子:
1 module test ( clk, reset, a, b ); 2 input clk; 3 input reset; 4 input [ 3:0 ] a; 5 output [ 3:0 ] b; 6 7 reg [ 3:0 ] tempa1, tempa2, b; 8 9 always @ ( posedge clk ) begin 10 if ( ! reset ) begin 11 tempa1<=0; 12 tempa2<=0; 13 b<=0; 14 end 15 else begin 16 tempa1<= a + 1’b1; 17 tempa2<= tempa1 + 1’b1; 18 b<= tempa2 + 1’b1; 19 end 20 end 21 endmodule
可以料見實現的電路:
下面借鑑了
波形圖:
可以看到強調的還是上面說過的非阻塞賦值的特點。
02 程式的可綜合性
實驗任務裡總有這麼一句:“用可綜合的程式碼......”
什麼是可綜合性,什麼又是不可綜合性呢?
下面學習了
這篇文章講的挺多,但是我現在這個RTL級還沒搞明白的菜鳥用不到這麼多,基本篩選如下:
不可綜合的Verilog語句:
-
initial
只能在test bench中使用,不能綜合。
-
assign 和deassign
不支援對reg 資料型別的assign或deassign進行綜合,支援對wire資料型別的assign或deassign進行綜合。
-
fork join 不可綜合,可以使用非塊語句達到同樣的效果。
這個塊是並行執行的,但是不可綜合。
敏感列表裡同時帶有posedge和negedge 如:(現在也碰不到
1 always @(posedge clk or negedge clk) 2 begin 3 ... 4 end
03 面積和速度的轉換
這裡我們說的面積:設計所佔用的FPGA邏輯資源數目,一般用所消耗的觸發器和查詢表(還沒學)來衡量。
速度:是指在晶片上可以穩定執行時能達到的最高頻率
兩者效能上的調配方法:
-
模組複用
-
串並變換
可以看到這種串並轉換,用更大的面積(即多個子模組並行),達到高頻率的效果。這一點後面還會再提到。
-
流水線
04 同步電路的設計原則
同步設計的優點:
-
可以有效避免毛刺的影響,提高設計的可靠性
-
可以簡化時序分析過程
-
可以減少工作環境對設計的影響
設計原則:
(由於應用還不多,對這些體會還不深刻,簡單記錄:
-
單時鐘
全域性時鐘網路的時鐘是效能最優,最便於預測的時鐘,具有最強的驅動能力
-
單時鐘沿
混合時鐘會使時序分析複雜、電路工作頻率降低
-
避免使用門控時鐘
-
即時鐘不要與組合邏輯再進行組合,如下:
-
-
可能引起毛刺、偏移
-
-
在模組內部不要再產生時鐘了。
05 模組劃分的設計原則
封裝複用
這有點像C++的封裝。
上一層模組只負責下一層模組的依據(即原材料),而具體行為互不相關。
這樣就保證了各個模組的相對獨立性和內部結構的合理性,便於維護,也使得相同邏輯可以複用同一模組。
同步時序模組的暫存器劃分原則
在設計時,應儘量將模組中的同步時序邏輯輸出訊號以暫存器的形式送出,以便於綜合工具區分時序和組合邏輯;
並且時序輸出的暫存器應符合流水線設計思路,能工作在更高的頻率,以極大地提高模組吞吐量。
流水線設計思路是什麼?
就是將組合邏輯系統地分割,並在各個部分(分級)之間插入暫存器,並暫存中間資料的方法。 目的是將一個大操作分解成若干的小操作,每一步小操作的時間較小,所以能提高頻率,各小操作能並行執行,所以能提高資料吞吐率(提高處理速度)。
06 通用程式碼風格
邏輯複用與邏輯複製
對於我這個初學者來說,邏輯複用和邏輯複製十分相似,瞭解後就知道確實不同,主要是涉及效能衡量尺度:速度和麵積的統籌。
-
邏輯複用是通過提高工作頻率來節省面積的優化方法,經常用於存在多個資源可共享單元的設計中。
-
PS:相當於為了節省人力,而讓一個人幹三個人的活。
-
10MHZ乘法器
-
兩個5MHZ乘法器
-
-
邏輯複製——面積換速度
-
邏輯複用是通過增加面積而改善設計時序的優化方法,經常用於調整訊號的扇出。
-
舉例就類似於上面的面積換速度
-
-
邏輯結構
-
鏈狀結構
-
樹狀結構
if和case語句的使用原則
-
If語句指定了一個有優先順序的編碼邏輯,
而case語句生成的邏輯是並行的,不具有優先順序。
這裡的if的優先順序就引起一些其他問題:
1 always@(in0,in1,in2,in3) 2 begin 3 sel = 2’b00 ; 4 if(in0) sel = 2’b00 ; 5 else if(in1) sel = 2’b01 ; 6 else if(in2) sel = 2’b10 ; 7 else if(in3) sel = 2’b11 ; 8 end 9 //當這裡in0和in1都==1時,se1=2'b00而不是2'b01; 10 //這就是if-else的優先邏輯。
而case是並行的,沒有優先順序。
-
if語句可以包含一系列的表示式;/有時甚至一個else就可以是一個二路選擇器。
而case語句比較的是一個公共的控制表示式。整個語句塊一起構成了一個多路選擇器。
這個很好理解。
-
通常if-else結構速度較慢,但佔用的面積小;
case語句結構速度較快,但佔用的面積較大。
-
巢狀的if語句如果使用不當,就會導致設計的更長延時。
-
如果想利用if語句來實現那些對延時要求苛刻的路徑,應將最高優先順序給最遲到達的關鍵訊號。(最小生成樹思想)。
-
有時為了兼顧面積和速度,可以將if和case語句合用。
舉例
1 //if-else實現四選一 2 module sdata_if (clk, reset, x, s, y); 3 input clk; 4 input reset; 5 input [3:0] x; 6 input [1:0] s; 7 output y; 8 9 reg y; 10 always@(posedge clk)begin 11 if(!reset)begin 12 y<=0; 13 end 14 else begin 15 if(s==2'b00) 16 y<=x[0]; 17 else if (s==2'b01) 18 y<=x[1]; 19 else if (s==2'b10) 20 y<=x[2]; 21 else 22 y<=x[3]; 23 end 24 end 25 endmodule
想象一下是什麼電路?
就是一個四選一,需要兩位的控制訊號,這個控制訊號就正好是s。
1 //case語句 2 module sdata_if (clk, reset, x, s, y); 3 input clk; 4 input reset; 5 input [3:0] x; 6 input [1:0] s; 7 output y; 8 9 reg y; 10 always@(posedge clk)begin 11 if(!reset)begin 12 y<=0; 13 end 14 else begin 15 case(s) 16 2'b00: y<=x[0]; 17 2'b01: y<=x[1]; 18 2'b10: y<=x[2]; 19 2'b11: y<=x[3]; 20 end 21 end 22 endmodule 23 24
實現電路也是類似上面的電路。
避免意外鎖存器
鎖存器在課本里學過,是用於儲存一位資料的元件,電平觸發。
其特點是:鎖存器在不鎖存資料時,輸出隨輸入變化;但一旦資料鎖存時,輸入對輸出不產生任何影響。
而我們在設計電路時,應該避免無意之間產生這種鎖存器,否則會導致一些邏輯上的錯誤。
引起意外鎖存器的原因
翻閱了很多網上的總結。有一些規則比較複雜,考慮到我現在的水平,我只記錄對初入門水平夠用且好理解的方面。後續繼續學習可以深入瞭解更多。
-
if……else……結構中缺少else
-
case結構中的分支沒有包含所有情況且沒有default語句。
建議
-
如果使用if語句,最好寫上else分支;
-
如果使用case語句,最好寫上default語句。
-
即使需要鎖存器,也通過else分支或default分支來顯式說明。而不要利用語言特性觸發生成(因為不可控)
內容真的好多,一時間難以完全消化...
上傳於2021年11月25日23時