|
勇敢的芯伴你玩轉Altera FPGA連載39:Verilog代碼風格之提升系統性能的代碼風格 特權同學,版權所有 配套例程和更多資料下載鏈接: http://pan.baidu.com/s/1i5LMUUD
下面要列舉的代碼示例是一些能夠起到系統性能提升的代碼風格。在邏輯電路的設計過程中,同樣的功能,可以由多種不同的邏輯電路來可以實現,那么就存在這些電路中孰優孰劣的討論。因此,帶著這樣的疑問,我們也一同來探討一下幾種常見的能夠提升系統性能的編碼技巧。請注意,本知識點所涉及的代碼更多的是希望能夠授人以“漁”而非授人以“魚”,大家重點掌握前后不同代碼所實現出來的邏輯結構,在不用的應用場合下,可能會有不同的邏輯結構需求,那么大家就要學會靈活應變并寫出適合需求的代碼。 ① 減少關鍵路徑的邏輯等級 在時序設計過程中遇到一些無法收斂(即時序達不到要求)的情況,很多時候只是某一兩條關鍵路徑(這些路徑在器件內部的走線或邏輯門延時太長)太糟糕。因此,設計者往往只要通過優化這些關鍵路徑就可以改善時序性能。而這些關鍵路徑所經過的邏輯門過多往往是設計者在代碼編寫時誤導綜合工具所導致的,那么,舉一個簡單的例子,看看兩段不同的代碼,關鍵路徑是如何明顯得到改善的。 這個一個簡單的例子要實現如下的邏輯運算: y = ((~a & b & c) | ~d) & ~e; 他們的運算真值表如表5.2所示。 表5.2 運算真值表
注:x表示可以任意取0或1。 按照常規的思路,我們可能會寫出如下的代碼: // Verilog例程 module example(a, b,c, d, e, y); input a,b,c,d,e; output y; wire m,n; assign m = ~a & b& c; assign n = m | ~d; assign y = n &~e; endmodule 使用Quartus II自帶的綜合工具,我們可以看到它的RTL視圖如圖5.10所示,和我們的代碼相吻合。
圖5.10 未優化前綜合結果 而現在假定輸入a到輸出y的路徑是關鍵路徑,影響了整個邏輯的時序性能。那么,下面我們就要想辦法從這條路徑著手做一些優化的工作。很簡單,我們的目標是減少輸入a到輸出y之間的邏輯等級,目前是3級,我們可以想辦法減少到2級甚至1級。 我們來分析公式“y = ((~a & b & c) | ~d) & ~e;”,把~a從最里面的括號往外提取一級就等于減少了一級邏輯。我們簡單的分析,當a=0時,y = ((b & c) | ~d)& ~e;當a=1時,y = ~d & ~e。由此我們不難得到“y = ((~a| ~d) & ((b & c) | ~d)) & ~e;”與前式是等價的。我們可以修改前面的代碼如下: // Verilog例程 module example(a, b,c, d, e, y); input a,b,c,d,e; output y; wire m,n; assign m = ~a | ~d; assign n = (b &c) | ~d; assign y = m & n& ~e; endmodule 經過修改后的代碼綜合結果如圖5.11所示,雖然b和c到y的邏輯等級還是3,但是關鍵路徑a到y的邏輯等級已經優化到了2級。與前面不同的是,優化后的d信號多了一級的負載,也多了一個邏輯門,這其實也是一種“面積換速度”思想的體現。正可謂“魚和熊掌不可兼得”,在邏輯設計中我們往往需要在“魚和熊掌”間做抉擇。
圖5.11 優化后綜合結果 上面的這個實例,只是一個也許未必非常恰當的“魚”的例子。在前面的章節里已經介紹過,在實際工程應用中,類似的邏輯關系可能在映射到最終器件結構時并非以邏輯門的方式來表現,通常是四輸入查找表來實現,那么它的優化可能和單純簡單邏輯等級的優化又有些不同,不過希望大家能在這個小例子中學到“漁”的技巧。 ②邏輯復制(減少重載信號的散出)與資源共享 邏輯復制是一種通過增加面積來改善時序條件的優化手段。邏輯復制最主要的應用是調整信號的扇出。如果某個信號需要驅動的后級邏輯信號較多,換句話說,也就是其扇出非常大,那么為了增加這個信號的驅動能力,就必須插入很多級的Buffer,這樣就在一定程度上增加了這個信號的路徑延時。這時可以復制生成這個信號的邏輯,用多路同頻同相的信號驅動后續電路,使平均到每路的扇出變低,這樣不需要插入Buffer就能滿足驅動能力增加的要求,從而節約該信號的路徑延時。 資源共享和邏輯復制恰恰是邏輯復制的一個逆過程,它的好處就在于節省面積,同時可能也要以速度的犧牲為代價。 看一個實例,如下: // Verilog例程 module example(sel,a, b, c, d, sum); input sel,a,b,c,d; output[1:0] sum; wire[1:0] temp1 ={1'b0,a}+{1'b0,b}; wire[1:0] temp2 ={1'b0,c}+{1'b0,d}; assign sum = sel ?temp1:temp2; endmodule 該代碼綜合后的視圖如圖5.12所示,和我們的代碼表述的一致,有連個加法器進行運算,結果通過2選1選擇器后輸出給sum。
5.12 兩個加法器的視圖 同樣實現這個的功能,我們還可以這么編寫代碼: // Verilog例程 module example(sel,a, b, c, d, sum); input sel; input[7:0] a,b,c,d; output[7:0] sum; wire[7:0] temp1 = sel? a:c; wire[7:0] temp2 = sel? b:d; assign sum =temp1+temp2; endmodule 綜合后的視圖如圖5.13所示,原先兩個加法器我們現在用一個加法器同樣可以實現。而原先的一個2選1選擇器則需要4選2選擇器(可能是兩個2選1選擇器來實現)替代。如果在設計中加法器資源更寶貴些,那么后面這段代碼通過加法器的復用,相比前面一段代碼更加節約資源。
圖5.13 一個加法器的視圖 ③ 消除組合邏輯的毛刺 在章節3.2的最后部分對于組合邏輯和時序邏輯的基本概念做了較詳細的介紹,并且列舉了一個實例說明時序邏輯在大多數設計中更由于組合邏輯。組合邏輯在實際應用中,的確存在很多讓設計者頭疼的隱患,例如這里要說的毛刺。 任何信號在FPGA器件內部通過連線和邏輯單元時,都有一定的延時,正是我們通常所說的走線延時和門延時。延時的大小與連線的長短和邏輯單元的數目有關,同時還受器件本身的制造工藝、工作電壓、溫度等條件的影響。信號的高低電平轉換也需要一定的上升或下降時間。由于存在這些因素的影響,多個信號的電平值發生變化時,在信號變化的瞬間,組合邏輯的輸出并非同時,而是有先有后,因此往往會出現一些不正確的信號,例如一些很小的脈沖尖峰信號,我們稱之為“毛刺”。如果一個組合邏輯電路中有毛刺出現,就說明該電路存在“冒險”。 下面我們可以列舉一個簡單例子來看看毛刺現象是如何產生和消除的。如圖5.14所示,這里在圖5.10所示實例的基礎上對這個組合邏輯的各條走線延時和邏輯門延時做了標記。每個門延時的時間是2ns,而不同的走線延時略有不同。
圖5.14 組合邏輯路徑的延時標記 在這個實例模型中,我們不難計算出輸入信號a、b、c、d、e從輸入到輸出信號y所經過的延時。通過計算,可以得到a、b、c信號到達輸出y的延時是12ns,d到達輸出y的延時是9ns,而e到達輸出y的延時是7ns。從這些傳輸延時中,我們可以推斷出,在第一個輸入信號到達輸出端y之前,輸出y將保持原來的結果;而在最后一個輸入信號到達輸出端之后,輸出y將獲得我們期望的新的結果。從本實例來看,7ns之前輸出y保持原結果,12ns之后輸出y獲得新的結果。那么這里就存在一個問題,在7ns和12ns之間的這5ns時間內,輸入y將會是什么狀態呢? 如圖5.15所示,這里列舉一種出現毛刺的情況。假設在0ns以前,輸入信號a、b、c、d、e取值均為0,此時輸出y=1;在0ns時,b、c、d由0變化為1,輸出y=1。在理想情況下,輸出y應該一直保持1不變。但從我們的延時模型來看,實際上在9ns到12ns期間,輸出y有短暫的低脈沖出現,這不是電路應該的狀態,它也就是這個組合邏輯的毛刺。
圖5.15 邏輯延時波形 既然我們的多個輸入信號的變化前后取值都保持高電平,那么這個低脈沖的毛刺其實不是我們希望看到的,也很可能在后續電路中這個毛刺導致后續的采集出現錯誤,甚至使得一些功能被誤觸發。 好了,言歸正傳,要消除這個毛刺,通常有兩個辦法,一個辦法是硬辦法,如果在y信號上并聯一個電容,便可輕松的將這類脈沖寬度很小的干擾濾除。但是,我們現在是在FPGA器件內部,還真沒有這樣的條件和可能性這么處理,那么只能放棄這種方案。另一種辦法其實也就是引入時序邏輯,用寄存器多輸出信號打一拍,這其實也是時序邏輯明顯優于組合邏輯的特性。 如圖5.16所示,在原有組合邏輯的基礎上,我們添加了一個寄存器用于鎖存最終的輸出信號y。
圖5.16 寄存器鎖存組合邏輯輸出 如圖5.17所示,在引入了寄存器后,新的最終的輸出yreg不再隨意的改變,而是在每個時鐘clk的上升沿鎖存當前的輸出值。
圖5.17 寄存器鎖存波形 引入時序邏輯后,并不是說完全就不會產生錯誤的數據采集或鎖存。在時序邏輯中,我們只要遵循一定的規則就可以避免很多問題,如保證時鐘clk有效沿前后的數據建立時間和保持時間內待采集的數據是穩定的。 | ||||||||||||||||||||||||||||||||||||||||||||||||