????????在前面一章介紹完施加約束之后,接下來要做的工作就是將設計進行綜合編譯(compile),本文我們將主要討論綜合編譯的過程。主要分為這樣幾個部分:
- 優化的三個階段及其特點
- 編譯的策略
- 編譯層次化的設計
一、優化的三個階段
????????這一節我們介紹Design Compiler進行優化的三個階段:結構級、邏輯級以及門級,在不同的階段,DC運用的方法和優化余地是不一樣的,我們將對這幾個階段的特點和優化方法進行一個介紹,這里一起歸納一下,希望能加深認識。
????????上圖是這三個階段的關系圖,可以看到,結構級屬于最高的抽象層次,當讀入Verilog代碼或者沒有經過映射的db文件后,DC的優化從這個階段層次開始,可以說,結構級是優化的最高層次,所以對DC來說,這個層次的綜合可以稱為高層次綜合(High-Level Synthesis)。結構層的下一個優化階段是邏輯級階段,也是讀入映射過的db文件的DC的初始優化階段。再往下一個階段是門級階段,也是優化的最后階段,這里所要作的工作主要就是GTECH到工藝庫的映射。
1、結構級優化
????????因為結構級是優化過程中層次最高的一級,因此它也是DC可以采用的優化方法最多的一級,它的主要優化方法如下圖所示
1)DesignWare選擇
????????DW選擇是結構級優化的一個很主要的特點,在這個階段DC能夠根據設計者施加的時序或者面積的約束在DW的不同實現方式中找到它認為最佳的實現方案。比如加法器的實現方式一共有如下幾種
????????其中DW Foundation需要有專門的license,而且使用之前還要設置綜合庫(synthetic library)。?
2)共享子表達式(Sub-Expressions)
????????這里的子表達式主要是指數學表達式,比如下面這個例子,如果按照原來的語句綜合,會包含6各加法器,但是如果表達式之間的公共項提取出來,便可以大大的減小面積,如下圖
????????如果要直接綜合出共享后的電路,可以在編寫RTL代碼的時候強制指定共享項:
3)資源共享(Resource Sharing)
????????資源共享的原理與共享子表達式類似,只不過這里指的所謂資源是一些HDL的運算符和表達式,比如加(+)、減(-)、乘(*)、除(/)以及大于(>)、大于等于(>=)、小于(<)、小于等于(<=)。比如給定一個語句
????????DC會根據具體的約束條件綜合出最符合要求的結構來。
4)運算符排序(Operator Reordering)
????????對于下面這個表達式Z <= A + B + C + D(輸出Z是施加了一定時序約束),DC最初是按照從左至右的順序計算的,也就是說它最初的排序如下
????????如果幾個輸入信號到達的時間相同,DC會通過運算符排序優化成下圖的平衡的結構,減小延時:
????????如果A信號較遲到達,則綜合出的電路結構會如下 :
2、邏輯級優化
????????在經過結構級優化之后,電路被轉化成了工藝無關的GTECH庫的形式,這級也稱為邏輯級,對于邏輯級優化來說,只有一個方法,那就是——結構化 (structure)或者扁平化(flatten)。
1)結構化(structure)
????????結構化是DC在邏輯級的默認的優化方法,它是指:使用電路的一些中間項構成一個多級的電路結構。如下圖的電路一共有三級邏輯,下一級的輸入是上一級的輸出,使用這種優化方法一般情況下能綜合出兼顧時序和面積的電路來。
????????結構化電路的典型是奇偶校驗電路。
2)扁平化(flatten)
????????扁平化是將所有的組合邏輯打平成乘積項和(SOP)的兩級結構,類似與可編程陣列邏輯(PAL)。使用這種結構的特點由于沒有利用中間項,綜合后電路面積將會變得很大,但是卻不一定能取得較好的時序。
????????扁平化結構的電路和設置扁平化的DC命令如下所示:
dc_shell > set_flatten true -effort low | medium | high
綜合結構化和扁平化的特點,可以歸納如下:
????????由于DC默認是用結構化的方式綜合邏輯級電路,而且這種方式可以得到兼顧時序和面積的結果,因此我們可以先用這種(結構化)方式優化。在優化后的電路中找出關鍵路徑,看看關鍵路徑上有沒有符合使用SOP電路的模塊,再將這些方便使用SOP的模塊set_flatten,以便取得最佳的效果。?
3、門級優化
????????門級優化是優化的最后階段,它所要完成的任務就是將GTECH的電路映射到最終的工藝庫中,并且保證映射后的電路不違反設計規則(Design Rule)。
1)工藝映射
????????工藝映射包括組合邏輯映射和時序邏輯映射。組合邏輯映射是指DC使用工藝庫中的各種門替換GTECH單元,并選擇能實現相同邏輯的符合時序及面積要求的單元。
????????時序邏輯映射的方法和組合邏輯相類似,也是出于速度和面積的考慮,盡量使用復雜的時序單元吸收一部分組合邏輯。?
?
2)設計規則檢查(DRC)
????????對于工藝庫的單元而言,Foundry都指定了每個單元的工作條件的限制,比如最大電容(max_capacitance)等等,這些限制也可以稱為設計規則(Design Rule),在設計規則限定的范圍內,Foundry提供的參數才有實際的意義。比如一個單元允許的最大電容為5pf,而實際工作電路中出現的電容值為10pf,那么這時,便違反了設計規則,單元的參數也就不能確保是準確的了。
????????因此,DC在作門級優化的時候,在映射的過程中也會檢查電路的設計規則,一般的做法是在單元中插入buffer增加驅動能力,或者將小驅動的單元替換為大單元。設計規則檢查分為兩個過程——DRC I 和DRC II 。
????????DRC I是指Design Compiler在不影響電路的時序和面積的前提下修正違反規則的一些單元,如果在這個前提下不能完全修正,則要進行下一步的檢查,即DRC II,這一步的修正必然是以犧牲一部分時序和面積為代價的。
二、編譯策略
????????編譯過程是指設計經過三個階段的優化,最終形成門級網表的過程,在這一節里,我們主要就編譯的策略,它包含如下幾方面的內容——
- 中斷編譯的方法
- 從報告中檢查時序,調整策略
- 修正保持時間違反(Hold time violations)
1、中斷編譯的方法
????????在DC-Tcl的界面下,當我們鍵入compile命令時,DC就開始了編譯,也就是優化的過程。優化是在設計規則的條件下,運用不同的算法,綜合最終出滿足時序和面積的電路。優化首先是時序驅動(timing-driven)的一個過程,其次再是面積。如果找到了一個滿足時序和面積等約束的電路,編譯將會停止;如果通過種種編譯仍不能滿足時序,編譯也會停止下來;另外,我們也可以人為的中斷編譯。
????????人為中斷編譯的方法是鍵入Ctrl-C,經過一段時間的等待后(有可能時間會很長),優化過程暫停,并彈出如下菜單:
????????這里有四個選項,設計者可以根據情況作出選擇。
????????DC在編譯的過程中,會自動打印出一個報表,報告編譯的總時間,設計的面積,關鍵路徑的時序違反和總共時序違反情況,我們可以根據需要更改打印的列項目:
2、分析報告,調整策略
????????一般情況下,我們先作一個默認的編譯,這樣一般可以取得既快又準確的結果,然后在編譯完成后使用一些報告時序的命令,并分析它們的輸出結果,使用的命令主要有
????????從這些報告中,我們可以看到電路中是否有違反的約束,如果有,那么它是什么類型,還有電路中的最大負裕量(worst negative slack)是多少,等等。下面我們就幾個常見的約束違反情況,談談糾正它的綜合策略
1)較大的時序違例
? ? ? ? 以下圖為例
????????從report_constraint –all這個命令的報告可以看出,需要到達的時間是1.20ns,而實際到達為2.84ns,違反了1.64ns。之所以判斷它是一個較大時序違反的情況,并不是因為1.64這個絕對值很大,而是相比較需要時間而言,1.64是一個較大的值。一般而言,如果電路中的最大負裕量(簡稱WNS)所占時鐘周期的15%以上的話,可以認為電路存在較大的時序違反。
??????確認存在較大時序違反之后,下一步就是找出原因,消除違反情況。可供參考的步驟有下面幾種:
- 檢查約束條件,看是否有疏漏或錯誤
- 檢查模塊劃分,看組合邏輯是否穿過多個模塊
- 重新編譯優化后的網表
- 修改RTL代碼???
重新編譯(Re-Compile)
????????當重新讀入映射后的網表進行重新編譯時,DC會自動將門級的網表重新返回到GTECH的結構,相當于邏輯級。然后分別進行邏輯級和門級的優化,但是同時也可以進行DesignWare的替換。
????????如果設計者僅僅將映射后的網表拿來再做一次compile,編譯后的結果并不會不一定會比原來的好,無非把以前做過的優化再跑一遍。因此,重新編譯之前會改變一些參數,如——改變設計約束、改變set_structure和set_flatten參數以及改變編譯的map_effort。
改變map_effort重新編譯
????????對設計進行編譯的時候,有三種編譯級別可以選擇,它們分別時低級、中級和高級。
dc_shell > compile?-map_effort low | medium | high
????????不同的級別編譯要求的編譯時間和編譯結果都各不相同,compile –map_effort low編譯時間最短,但是結果不一定好,它一般用于設計預估(Design Exploration),不用在重新編譯環節。
compile –map_effort medium是DC默認的編譯級別,大多能在一定的時間內得到較為滿意的結果。這也是我們推薦的初始編譯級別。
????????compile –map_effort high 編譯的過程中會使用前面的級別中沒有的算法,因此它所要求的時間是最多的,結果也是相對最好的。這種級別一般用在重新編譯的階段。
修改RTL代碼
????????修改源代碼所能取得的效果是最直接的,同時也是代價最高的。修改代碼后,DC會從最上層的結構級開始優化,前面也討論過,越上層次的優化方法越多。所以通常這樣得到的結果也越滿意。但是,修改代碼也不一定放之四海皆準的方法,因此并不是所有的設計我們都能獲得源代碼,同時也不是可以隨便修改的。
2)較小的時序違例
? ? ? ? 以下圖為例
????????從上圖看出,相比較1.20的允許路徑延時,0.10的負最大裕量(WNS)是比較小的(小于15%),而且已經認定了約束和模塊劃分都是正確的,那么應該怎樣修復這個錯誤呢?
????????這里主要講一下Incremental Mapping:
dc_shell > compile?-incremental_mapping
????????這個開關告訴DC,在重新編譯的時候不需要把網表返回到GTECH結構,因此也不需要作邏輯級優化,速度也較一般的編譯更省時間。這里DC所要作的是進行門級單元的替換,即在不違反設計規則的情況下用延時小的單元替換延時較大的單元。另外,如果讀入的是db格式的網表,在這個階段也可以進行DesignWare的替換。
????????如果要同時優化多條路徑,需要使用另外一個命令——set_critical_range。這個命令設置的critical_range是以WNS的值為基準的,優化的是和這個值的絕對值差設置值的那些路徑。因此,如果設置值為0,那么就僅僅優化一條關鍵路徑。
dc_shell > set_critical_range 2 [current_design]
3)設計規則違反
????????有些時候的時序違反是由于設計規則違反引起的,比如說一個單元的扇出(fanout)過大,導致它的transition time的時間迅速增加。對于這種情況,我們可以通過
dc_shell > report_net -connections -verbose
dc_shell > report_timing -net
????????兩個命令審查連線的連接和負載情況。
????????要修正設計規則的錯誤,可以使用一個編譯的開關:
dc_shell > compile -only_design_rule
????????如下面這個例子,為了滿足最大電容的規則,在A端口的內部加上了一個buffer,用于緩沖N路徑對A的負載。
3、修正保持時間違例
????????一個時序電路要想正常工作,除了必須滿足建立時間要求之外,也需要滿足保持時間要求。雖然保持時間檢查和建立時間檢查是同樣重要的,但是我們在實際綜合的過程中卻不是把它們同時考慮,而是更多的把保持時間的檢查放到布局后。這是因為
- 時鐘偏移必須要到布局完成后才能得到準確值
- 修正保持時間的通常做法是插入buffer,而這可能會增加建立時間違反的可能性,并且增大了組合電路的面積
- 保持時間檢查用的一般都是電路工作的最佳條件,而在這個條件下,連線延時往往是被忽略的,連線延時也是必須在布局后才準確。
????????如果確定要同時作建立和保持時間檢查,那么在施加電路約束的時候要加入相應的開關,如:
????????以及設置各自的工藝庫——?
????????
????????默認情況下,DC不修正保持時間的違反。如果確定要作修正,需要先設置一個變量再作檢查 :
dc_shell > set_fix_hold [all clocks]
dc_shell > compile -only_design_rule
????????加上only_design_rule的開關后,編譯過程中僅僅更換單元大小,并增加buffer,以便修正設計規則違反和保持時間違反。
三、層次化設計的編譯
????????一個層次化設計的編譯過程包含兩個階段:1、將所有的子模塊映射到門級,2、優化。
??????????在一個層次化的設計中,我們可能會遇到下面這種情況
??????
????????上圖中,被綜合的模塊中D_design中含有三個子模塊U1、U2和U3,其中U1和U3都是由模塊Ades例化而來,這里的Ades稱為多次例化的模塊。對于這樣一個設計,在compile之前使用check_design作檢查的時候會報一個warning,即設計中存在多次例化的模塊(multiple instantiations),如果在這種情況下,我們不考慮多次例化的模塊(Ades),那么在繼續的compile時候程序會終止退出。因此,必須對它進行處理,這里我們介紹兩種方法——uniquify和compile + dont_touch。?
1、uniquify
????????使用uniquify,DC會對每個例化的模塊作一份拷貝,然后對它們分別取一個名字,即把不同的例化模塊當作不同的兩個模塊處理。
????????注意看上圖,U1和U3兩個模塊的設計名分別由原來的Ades變成了Ades_0和Ades_1,因此在編譯時,DC會將它們當作兩個不同的模塊,這樣就可以根據它們不同的周圍環境作優化。
????????使用uniquify的具體實現方法如下
?dc_shell > uniquify
dc_shell > compile
2、compile + dont_touch
????????這種方法先將多次例化的模塊作單獨的約束和編譯,然后在整合到上一級模塊的過程中將它的屬性設置為dont_touch,再編譯。
????????上圖中,U1和U3兩個模塊的設計名都沒有變化,只是在編譯D_design之前先將Ades編譯一次。這樣U1和U3實際上是一模一樣的模塊。
????????compile+dont_touch的實現方法如下
????????這里的約束文件有兩個,一個是Ades的Aconstraints.tcl,另一個是D_design的Dconstraints.tcl,并且在source后一個約束文件之前要對編譯過的Ades設置成dont_touch。
在設置了dont_touch屬性之后,編譯D_design的時候就會忽略Ades,這樣有好處也有壞處,好處是可以保護模塊不被修改,但是這樣同時也限制了DC對U1和U3的進一步的優化。?
3、uniquify Vs compile + dont_touch
????????通過對上述兩種方法的介紹,我們不難看出它們各自的優缺點——
????????compile+dont_touch由于只需要對多次例化的模塊編譯一次,因此可以減少整個設計的編譯時間,也可以減少內存的使用量。在多次例化的模塊很復雜并且工作站的硬件條件有限的情況下,使用這種方法的優越性的比較明顯的。還有,如果這個Ades是一個第三方提供的硬核(hard-core),那么我們也只能使用這種方法。
????????使用這種方法的缺陷也是顯而易見的:由于頂層模塊在編譯的時候Ades設置了dont_touch,這就妨礙了DC針對Ades的各個實例周圍環境的不同的進一步優化,從而使得結果不能真實的反映各個實例周圍的環境變化。
????????uniquify由于把各個多例化模塊作為獨立的模塊來看,因此DC可以分別針對它們作出更好的優化,從而得到的結果也是比較理想的。缺點就是編譯的時間稍微較長,但是對于一些不大的模塊來說,這些是可以忽略的。????????
????????正因為uniquify可以綜合出更好的結果,所以如果一般推薦使用uniquify解決多例化模塊的綜合問題。