很多人在學習verilog的時候,總是搞不懂阻塞賦值與非阻塞賦值。其實兩者區分比較簡單。
阻塞賦值就和高級語言(如C、java)中的賦值一樣,寫法也一樣,都是直接用“=”。在語句塊中,都是上一條語句執行完畢后,再執行下一條語句。也就是說,如果語句A執行依賴語句B執行的結果,在語句B執行完之前,語句A一定處於阻塞等待狀態,待B的結果到達后再執行。
非阻塞賦值就需要有時序的概念,也就是流水作業,寫法為“<=”。看過農民工搬磚的同學都知道,幾個人站成一行傳遞磚塊,有個喊號子的人,號子一響,所有人把自己一只手上的磚傳給后一個人的同時,另一只手也接到前面一個人的磚,只要磚沒搬完,所有人手上總是有塊磚。號子就像時鍾,每個農名工就是一個觸發器(帶存儲功能),時鍾邊沿脈沖一到,就把當前的值傳出去,而自己也同時更新。也就是說,在語句塊中,如果語句A執行依賴語句B執行的結果,則在時鍾沿到達時,語句B把自己此時擁有值(也就是上一周期運算並暫存在此的結果)傳遞給A的同時,B又要完成本時鍾周期的運算;反過來說,語句A執行時,不需要阻塞着等待B完成本時鍾周期的運算,而是直接把B中現在存有的值(即上一周期執行結果)取過來。
寫verilog程序,腦子里一定要有“硬件電路”,對於賦值來說,你要清楚寫出來的東西到底是“一根線”還是一個“觸發器”,當然還有“鎖存器”。對於阻塞賦值來說,“=”左右的邏輯是用線連接;而非阻塞賦值,“<=”左右使用觸發器連接,觸發器通常由時鍾邊沿觸發控制。在邏輯綜合時,阻塞賦值直接被綜合成一堆組合邏輯,大家連成一片,屬於一個周期內的運算;非阻塞邏輯被綜合成時序邏輯,語句之間會添加觸發器(帶寄存器功能),大家干活由時鍾控制,不在一個周期內工作。
我個人比較傾向於用assign語句進行阻塞賦值,一片組合邏輯寫在一起;如果一定要使用always語句塊寫組合邏輯,則敏感表一定要寫全乎了,少一個都會出意想不到的問題;組合邏輯之間用always語句塊連接進行時序控制,always的敏感表里只有clk和reset信號,內部語句塊都是非阻塞賦值。這樣邏輯比較清晰。
再談談上面提到的“鎖存器”。這個也很好理解。在高級語言(如C、java)中,寫了if,可以不用寫else;case語句,如果沒有考慮到所有的情況,寫不寫default,對高級語言執行也沒太大影響(所以說,高級程序員幸福啊!)。但是,verilog不一樣,一定要記住,你寫的語句都要變成電路,if-else可以理解為一個2選1的選擇器,寫了if不寫else,那電路在else的時候就不知道該怎么辦,不知道該怎么辦那就只能保持不變;case也一樣,可以理解為一個多路選擇器,指明運算電路知道怎么處理,未指明的電路就只能保持原值,這一保持就生成了“鎖存器”。我個人很不喜歡“鎖存器”這個東西,一是覺得沒else、沒default邏輯不完整,硬件工程師有強迫症受不了;二是這種隱藏賦值使得程序不好理解,都是明白人,有事擺在面上說合適,性格直率沒辦法;三是鎖存器會隱形增加硬件面積功耗,FPGA資源太金貴,節約慣了沒辦法。在絕大多數設計中避免產生鎖存器,還有一個很重要原因,它會讓你設計的時序完蛋,並且它的隱蔽性很強,非老手不能查出。鎖存器最大的危害在於不能過濾毛刺。這對於下一級電路是極其危險的。所以,只要能用觸發器的地方,就不用鎖存器。