天天看點

FPGA之道(59)時空變換之時域優化

文章目錄

  • ​​前言​​
  • ​​時空變換之基本概念​​
  • ​​時空概念簡介​​
  • ​​時空變換方案​​
  • ​​時空變換之時域優化​​
  • ​​邏輯化簡​​
  • ​​邏輯化簡讨論​​
  • ​​空域方面的颠倒現象​​
  • ​​時域方面的颠倒現象​​
  • ​​邏輯化簡總結​​
  • ​​結構調整​​
  • ​​分布調整​​
  • ​​思路轉換​​
  • ​​提前進位法提高多級累加器的工作頻率​​
  • ​​布局調整​​

前言

疫情已經持續到了3月,今天是三月初,前幾日在修改論文,想來今天是新的一月的開始,應該更新部落格了,日子雖然都是數字,但有些日子注定擁有特殊的記号。

本來想着應該更新數的表示以及數的運算,但是确實在FPGA中完成原本我們認為十分簡單的東西具有一定的難度,十分的繁瑣,還是更新下FPGA設計的思路,今天是有關時空轉換的,為了博文不能太長,選擇分為幾部分來更新。

《FPGA之道》,讓我們站在巨的肩膀上,開始沖鴨。

時空變換之基本概念

時空概念簡介

時脈速度決定完成任務需要的時間,規模的大小決定完成任務所需要的空間(資源),是以速度與規模其實就是FPGA設計中時間與空間的展現。對于FPGA設計來說,時間和空間這兩因素就好比陰、陽之于太極,乾、坤之于八卦一樣,它們互相依存卻又互相制約,唯有達到平衡,方可天下太平。

如果要提高FPGA設計的時鐘速率,那麼每一個時鐘周期内組合邏輯所能做的事情通常就越簡單,是以為了實作同樣的功能往往會造成資源的膨脹。與此同時,由于時脈速度越快,對資源排列關系的要求就越緊湊,可是資源的膨脹會導緻資源占用率提高,進而導緻在FPGA晶片内進行布局布線的難度增大,這也就導緻資源更難變得緊湊,進而又會給時脈速度的提高帶來一定的負面影響。

如果要降低FPGA設計的規模,一般來說,以前多個子產品做的事情就需要由一個子產品來完成,那麼為了能在同樣的時間内完成同樣的任務,那麼就必須提高該子產品的執行速度,即時鐘頻率,而時鐘頻率的提高又可能會導緻該子產品一定的資源膨脹,進而又會給設計規模的降低帶來一定的負面影響。

上面的讨論中蘊含了FPGA設計的時空互換定律,那就是——如果時間吃緊,空間有餘,就拿空間來換時間,雖然空間的增加可能對時間又會産生一些負面影響,但是通常利大于弊;反之,如果空間吃緊,時間有餘,就拿時間來換空間,雖然時間的減少可能對空間又會産生一定負面影響,但是通常利也是大于弊的。

鑒于此,可以推斷出:如果FPGA設計的規模預期比較大,那麼它布局布線的難度就會較大,同一功能聚類的資源就較難緊密排列,是以時脈速度就不可能太高,如果FPGA設計的時間預期也比較高,那麼由于時脈速度的限制,要在規定的時間内完成同樣的任務就需要更多的資源來實作任務分擔,這又反過來導緻了設計規模的變大。是以現實中,不可能存在時間、空間均最優的FPGA設計,若要這麼做,無異于癡人說夢。但是殘酷的現實往往會逼迫每一名FPGA開發者朝這個方向進行努力,因為随着內建度的越來越高、功能需求的越來越複雜等等因素,客觀環境要求我們必須設計出又快又省的FPGA設計才能夠最終完成項目、迅速赢得市場。是以,為了滿足各種苛刻的需求,每一個FPGA開發者都必須了解并能運用時空變換的相關技術,以使自己的設計達到時間、空間的綜合最優。

那麼由時空互換定律出發,凡是能夠在這兩種因素之間做到随意調配的人,必是FPGA設計師中的“大蝦”!凡是能夠根據實際情況達到時空完美平衡點的作品,必是FPGA設計中的“傑作”!是以,寫HDL代碼前先對自己的FPGA設計做一個時空的評估,找準設計的時空平衡點,是指導整個設計走向的重要參考。

時空變換方案

當FPGA設計的性能或者預期的性能不能滿足項目的需求時,就是需要運用時空變換定理的時候。總的來說,時空變換定理在運用時可以具體劃分為四項工作内容,即——時域優化、空域優化、時間換空間以及空間換時間。 那麼當我們需要對FPGA設計的性能進行優化時,通常的做法應該是這樣的:

  • 首先,利用時域優化來去除設計在時間方面的備援,利用空域優化來去除設計在空間方面的備援。如果這樣處理後的設計已經能滿足需求,那最好不過;即使仍然達不到要求,經過如此處理後的設計也分别在空間和時間上騰出了更多的餘量,而餘量是時空變換定理得以應用的前提。
  • 其次,根據設計與需求之間的沖突,如果是時間不夠了,那麼就想辦法用空間換時間;如果是空間不夠了,那麼就想辦法用時間換空間。
  • 第三,由于一些時空變換的方法會改變原有邏輯電路的組織結構,是以變換後的邏輯電路很可能具有進一步進行時域優化、空域優化的餘地,是以可以利用這一點進一步提升邏輯電路的性能。
  • 最後,在FPGA設計中,沒有什麼方案是永遠最優的,也沒有什麼方案是永遠最差的,我們所要做的并不是竭盡全力去找尋最優的方案,而應該是集中精力找到最适合目前需求的方案。

上述即為常用的時空變換方案,接下來将具體讨論時空變換時所涉及到的四類工作:

時空變換之時域優化

本小節我們主要關注時域内部的優化方法。注意,在做時域優化時,往往是不以犧牲空域餘量為前提的,因為它的目的主要是去除時域内部的一些備援。接下來,将為大家介紹幾類比較常用的時域優化方法:

邏輯化簡

邏輯化簡讨論

邏輯化簡是最最基本的備援去除方法,也是最最有效的備援去除利器。無論是針對組合邏輯還是時序邏輯,異或是針對時域還是空域,邏輯化簡會給所有方面都帶來好處。因為邏輯簡化了,首先實作該功能需要的邏輯門和觸發器數量往往就會減少,是以組合邏輯和時序邏輯中的備援都得以剔除,進而對空域帶來了優化效果;其次,組合邏輯更加精簡了,布局布線更加容易了,時域更容易達到較好的性能,并且邏輯的化簡很可能導緻組合邏輯的級數減少,而這将直接對時域的性能産生大幅提升。

當然,編譯器在對FPGA設計進行編譯的過程中,也會同時對邏輯進行一些優化,不過其程度畢竟有限。并且,作為一個合格的FPGA開發者來說,應該更多的掌握自己的命運,而不是交由編譯器去處置。是以在進行FPGA設計及HDL代碼描述時,我們還是應該注重多做一些邏輯化簡工作的,不求将所有邏輯都化簡到最簡,但求不要在我們的FPGA設計中存在過多較為明顯的備援。

邏輯化簡的方法有很多種,這裡就不再贅述。不過由于FPGA内部硬體組織結構的特殊性,導緻有時候略微複雜一些的邏輯實作起來并不一定比簡單的邏輯占用資源多,甚至有時候資源更省,空域性能更好;也會導緻有時候略微複雜一些的邏輯實作起來并不一定比簡單的邏輯時間延遲大,甚至有時候延遲更小,時域性能更好。當然了,這種由于FPGA硬體結構導緻的邏輯時空性能颠倒僅僅是細節方面的微觀可能,大部分時候其變化趨勢仍然是與邏輯化簡的程度保持一緻的,而從宏觀上來看,仍然是越簡單的邏輯,其時空性能越好。

其實,在FPGA中,會導緻時空性能出現反常現象的主要原因就是其内部的查找表(LUT)資源。通過【知己知彼篇->FPGA内部資源介紹->邏輯資源塊】小節的介紹,我們了解到,FPGA内部實作組合邏輯的主要載體就是LUT資源。通常來說,為了使每個LUT都能夠實作功能适中的組合邏輯,FPGA内部的LUT一般都是3、4、5或6輸入的,但輸出都僅有1個。是以,編譯器總是要面臨着将我們所描述的組合邏輯映射為多輸入、單輸出的LUT集合形式的工作,而這一工作,就有可能導緻微觀上的時空性能颠倒。不過随着編譯器版本的不斷更新,編譯技術的不斷進步,這種微觀上的時空性能颠倒的可能性也會越來越小,但是作為一個FPGA開發者來說,還是有必要了解一下這種情況的具體表現和成因的,下面就來具體介紹:

空域方面的颠倒現象

有時候,邏輯上的化簡并不會為最終的FPGA資源占用帶來好處,例如對于以下邏輯:

FPGA之道(59)時空變換之時域優化

這是一個3輸入、1輸出的組合邏輯,存在着明顯的備援,可以化簡為:

F = AB + AC +BC;

化簡後的組合邏輯顯然會消耗更少的邏輯門,但仍為3輸入、1輸出的組合邏輯。那麼對于一個内部僅有4輸入、1輸出LUT的FPGA晶片來說,這一邏輯化簡過程是毫無任何意義的,因為無論是原來存在備援的3輸入組合邏輯還是化簡後的3輸入組合邏輯,它們的輸出都僅有1個,是以編譯器最終都是使用一個4輸入、1輸出的LUT來實作上述邏輯函數功能的,是以化簡前後對于FPGA内部資源的占用沒有任何影響。

有時候,邏輯上的化簡甚至還會為最終的FPGA資源占用帶來更多的負擔,例如對于以下邏輯:

F = AB + CD + MN;

這是一個精簡的6輸入、1輸出組合邏輯,并不存在備援,但是如果FPGA晶片内部仍然僅有4輸入、1輸出的LUT,那麼将該組合邏輯映射為LUT的結果大概如下圖所示:

FPGA之道(59)時空變換之時域優化

上圖中,每一個虛線框内的組合邏輯部分都被映射為FPGA晶片内部的1個LUT資源,是以上述組合邏輯共占用3個4輸入、1輸出的LUT資源。注意,左下部分的兩個小虛線框顯然沒有充分利用LUT的4輸入特性,但是由于它們各有輸出,是以也無法合并。

倘若略微複雜一下上述組合邏輯,将其變化為:

F = (AB + CD) + MN;(注意,兩個兩輸入或門比一個三輸入或門要耗資源)

那麼新的映射結果将如下圖所示:

FPGA之道(59)時空變換之時域優化

從上圖可以看出,這一個略微複雜一點的功能等價組合邏輯,共占用2個4輸入、1輸出的LUT資源,比之前節省的1個LUT資源。

繼續在上述邏輯中添加備援,将其變化為:

F = (AB + CD) + (MN + (AB + CD));

重新進行映射工作,可得新的結果如下:

FPGA之道(59)時空變換之時域優化

由此可見,即便是一個存在如此明顯備援的組合邏輯,最終也僅占用了FPGA晶片中的2個LUT資源。

時域方面的颠倒現象

有時候,邏輯上的化簡并不會為最終的FPGA時序名額帶來好處,例如對于以下邏輯:

F = (AB + BC)AC;

這是一個3輸入、1輸出的組合邏輯,存在着明顯的備援,可以化簡為:

F = ABC;

化簡後的組合邏輯顯然具有更少的邏輯門級(原來3級門延遲,現在僅1級門延遲),但仍為3輸入、1輸出的組合邏輯。那麼對于一個内部僅有4輸入、1輸出LUT的FPGA晶片來說,這一邏輯化簡過程是毫無任何意義的,因為無論是原來存在備援的3輸入組合邏輯還是化簡後的3輸入組合邏輯,它們的輸出都僅有1個,是以編譯器最終都是使用一個4輸入、1輸出的LUT來實作上述邏輯函數功能的,是以化簡前後LUT的級數都為1,是以化簡工作對于FPGA的時序名額是沒有任何影響的。

有時候,邏輯上的化簡甚至還會為最終的FPGA時間性能帶來額外的負擔,例如對于以下邏輯:

FPGA之道(59)時空變換之時域優化

上述是一個明顯具有備援的組合邏輯,如果FPGA晶片内部的一個邏輯資源塊具有六個4輸入、1輸出LUT和三個3輸入、1輸出的LUT,那麼上述具有備援的組合邏輯需要使用五個4輸入、1輸出的LUT來實作,正好可以放置于一個邏輯資源塊内。但若将上述組合邏輯化簡為如下形式:

F = ABC + ABD + ACD;

那麼此時需要四個3輸入、1輸出的LUT來實作,由于一個邏輯資源塊内最多隻有三個這種規格的LUT,是以,為了實作上述化簡後的邏輯必須跨越至少兩個邏輯資源塊。但是,這樣做的代價是要增加走線的長度,是以化簡後的邏輯在被映射為LUT的形式後,時域方面的性能反而更差了。

邏輯化簡總結

綜上所述,恰恰也正是由于這種FPGA編譯時從邏輯門到LUT的映射過程,會導緻太為細節的邏輯化簡對時空性能沒有影響甚至出現反常的現象,是以再加上編譯器本身的優化功能,便為廣大FPGA開發者們帶來了巨大的福音!那就是我們無需嚴格按照【共同語言篇->數字邏輯電路基礎知識->數字邏輯的化簡】章節中介紹的那些化簡方法,也無需絞盡腦汁、廢寝忘食的去關注每一部分組合邏輯并力求達到最簡,而僅需在我們的HDL代碼中稍作注意,剔除明顯且過分的組合邏輯備援即可。

結構調整

結構調整是提高時域性能的另一種方法,它是在不改變原有組合邏輯功能的前提下,通過調整其内部邏輯門之間的連接配接關系,來達到減少邏輯門級數的目的,進而提高時域性能的方法。舉例如下:

現在有同步輸入總線A、B、C、D,需要在下一時鐘周期就能以寄存的方式輸出它們的和SUM。那麼通常來說,你可能會将HDL代碼寫成這樣:

-- VHDL example 
-- all inputs are bus type  
  process(clk)
  begin
    if clk'event and clk = '1' then
      SUM <= A + B + C + D;
    end if;
  end process;
  
// Verilog example  
// all inputs are bus type  
  always@(posedge clk)
  begin
    SUM <= A + B + C + D;
  end      
對于上述代碼,編譯器通常會将它翻譯成如下電路:      
FPGA之道(59)時空變換之時域優化

由此可見,該電路中從“輸入A~D”到“輸出SUM的寄存器輸入”之間,最長的邏輯路徑依次經過了3個加法器,也就是說上述電路中的組合邏輯部分有3級加法器,如果一個加法器的時間延遲為T的話,那麼整個組合邏輯的時間延遲就是3T。

編譯器之是以會給出如此結構的電路,主要是因為在HDL語言中,“+”運算符号對應的運算優先關系為從左至右,是以A、B先參與運算,而其結果再和C一起送入下一個加法器運算,而這次的結果再和D一起送入第三個加法器進行運算,至此才得到組合形式輸出的和。通過進一步分析上述電路,我們可以發現,當A、B進行運算時,C、D其實閑着沒事幹(雖然事實不是這樣,但是此時第二、第三個加法器進行的運算都是無效的),因為第一、第二個加法器的輸出還沒有穩定;而當A、B或C參與有效運算時,D其實一直是閑着沒事幹的(解釋類似同前),因為第二個加法器的輸出還沒有穩定。由此可見,上述電路中的邏輯結構不是很合理,因為我們更希望編譯器得出的電路是同時使用兩個加法器分别處理A、B和C、D的求和,然後再使用一個加法器處理這兩個加法器的結果即可。為了達到這種效果,我們可以對上例進行一些修改,結果如下:

-- VHDL example
-- all inputs are bus type  
  process(clk)
  begin
    if clk'event and clk = '1' then
      SUM <= (A + B) + (C + D);
    end if;
  end process;
  
  // Verilog example  
  // all inputs are bus type
  always@(posedge clk)
  begin
    SUM <= (A + B) + (C + D);
  end      

可見,我們僅僅通過添加了兩個小括号,就調整了原有算式中不太合理的運算優先級關系,這便是“小括号的妙用”。對應上述代碼,編譯器給出的電路結構如下:

FPGA之道(59)時空變換之時域優化

由此可見,該電路中從“輸入A~D”到“輸出SUM的寄存器輸入”之間,最長的邏輯路徑依次經過了2個加法器,也就是說上述電路中的組合邏輯部分僅有2級加法器(雖然從資源的角度上來說它仍然包含3個加法器),那麼此時,整個組合邏輯的時間延遲減小到了2T,這極大的提高了上述求和電路在時域方面的性能。

關于上例,有一點需要注意,那就是參與運算的A ~ D全部都是總線,也就是說它們的資料位寬應該都大于1bit。如果資料位寬僅為1bit的話,編譯結果會略有不同,因為每個加法器還具有一位進位輸入位,這樣對于1bit的輸入來說,可以完成三個輸入的求和,那麼此時的小括号就要以3個輸入為一組來進行時域優化了。例如,若對于A~J這9個1bit位寬的輸入信号求和,時域優化後的寫法應該如下:

-- VHDL example
-- all inputs are 1 bit 
  process(clk)
  begin
    if clk'event and clk = '1' then
      SUM <= (A + B + D) + (E + F + G) + (H + I + J);
    end if;
  end process;
  
  // Verilog example  
  // all inputs are 1 bit
  always@(posedge clk)
  begin
    SUM <= (A + B + D) + (E + F + G) + (H + I + J);
  end      

編譯器給出的對應結果電路如下:

FPGA之道(59)時空變換之時域優化

當然了,真要是輸入全為1bit的話,加法也就完全變成了異或操作,我們也就沒有必要使用全加器來實作求和功能。

對比上述兩個運用了時域優化後的例子,可以發現結構調整的思路跟組合邏輯内部基本邏輯單元的輸入端口數量相關,而結構調整的最終目的是希望把各個輸入視為葉子節點,把輸出視為根節點,最終組建出深度最小的樹。(樹的概念可以參考《資料結構》相關書籍)

分布調整

分布調整又叫時間調整,英文用retiming來表示。它是基于這樣一個事實而産生的,即一個設計的時間性能取決于其中延遲最大的那部分組合邏輯,這類似短闆效應,原理詳見下圖:

FPGA之道(59)時空變換之時域優化

上圖中,由于組合邏輯1的複雜度遠高于組合邏輯2,是以整個時序電路的時間性能完全受制于組合邏輯1的時間延遲名額,而與組合邏輯2這部分無關。是以,在不犧牲空間餘量的前提下,在不改變原有邏輯功能的前提下,為了能夠讓“短闆變長”,可以采用從其它“長闆子”上截取一節并拼接到“短闆”上的方法,來達到時域性能的提升,原理詳見下圖:

FPGA之道(59)時空變換之時域優化

由此可見,該方法的具體操作就是,從時序電路中最複雜的組合邏輯1中拿走一小部分邏輯(組合邏輯1B),并将其移至之前邏輯複雜度較小的組合邏輯2所處的位置,這樣将使得原組合邏輯1的剩餘部分(組合邏輯1A)與新合并出的組合邏輯(組合邏輯1B+組合邏輯2)的複雜度更加均衡,以此便可以提高整個時序邏輯的時間性能。例如,我們需要對一個數X進行如下操作,先加上常數17,然後再按位取反,然後再減去常數37,最後再乘以常數23并得到結果Y。考慮到整個運算過程比較複雜,是以可以分兩步來完成整個求解過程,下圖便是一種方案:

FPGA之道(59)時空變換之時域優化

仔細觀察上圖,可以發現後兩個寄存器之間的組合邏輯處理了過多的内容,尤其是乘法的處理更是需要消耗更多的時間,是以這将成為整個時序邏輯的短闆,限制整個電路的時間性能。若加法、取反、減法操作的組合邏輯延遲均為T,乘法的為3T,那麼上述電路的“短闆”時間延遲達到5T,而另一塊闆的時間延遲僅為T,這是非常不合理的。是以,為了提高整個時序邏輯的時間性能,采用分布調整的方法對上述原理電路進行修改,得到新的方案如下圖所示:

FPGA之道(59)時空變換之時域優化

新的方案中,第一部分組合邏輯的時間延遲為3T,第二部分的組合邏輯時間延遲也是3T,是以整個方案的組合邏輯配置設定非常均衡,整個時序電路“短闆”的時間延遲僅為3T,比之前的方案多出了2T的餘量,這便是分布調整方法對于時域優化的作用。

最後需要說明一點,分布調整不同于邏輯拆分,因為它并沒有改變原有組合邏輯的塊數,而邏輯拆分則會增加原有組合邏輯的塊數,并且同時會增加空間方面的開銷。關于邏輯拆分的應用将會在【時空變換之空間換時間】小節中詳細介紹。

思路轉換

要解決一個問題,方法有很多,這其中必然有一些方法更巧妙或在某一方面更巧妙一些,也必然有一些方法更啰嗦或在某一方面更啰嗦一些。由于每個FPGA開發者的水準和經驗都有所不同,是以在解決FPGA設計問題時,所能想到的解決思路也會有所不同。那麼,當你在進行FPGA設計的時候,你所想出的思路将受限于個人的經驗、水準以及思考時間,一般來說很難到達最優或者在某方面最優。很多時候,隻要你所想到的方案能夠算得上次優的,已經算是很不錯了。而思路這個東西是宏觀的,它決定了整個FPGA設計大的架構和方向,一旦這個東西确定了,那麼最終邏輯電路的時空特性也就大緻定了型,即便後續我們做了充分的邏輯化簡、結構調整、分布調整等工作,也難對最終邏輯電路的時空特性産生較大的影響。是以,當我們的FPGA設計遭遇了瓶頸,換個解決思路往往能收到豁然開朗的奇效,有時候可能提升了時域性能,有時候可能提升了空域性能,有時候甚至能夠對時空兩方面的性能都帶來大幅的提升,當然,有時候也可能會對其中一方面或者兩方面都造成不好的影響。

例如,乘法的實作就有很多種思路:使用内嵌的DSP資源實作,這種思路的時域性能較好,但DSP資源一般比較緊俏;使用寄存器、查找表等邏輯資源實作,這種思路的時域性能較差,并且對邏輯資源的消耗也不小;使用BLOCK RAM建立乘法口訣表來實作,這種思路的時域性能也較好,但是對于位寬比較大的乘法,該口訣表可能會消耗太多的存儲空間;使用一個累加器,将A乘以B分解為A+A+……+A(B個A),這種思路很省資源,但是時間上面卻消耗太大;如果乘法中乘數是固定的,那麼則可以利用這一特性并結合移位運算來輕松實作乘法,這樣的話對時域和空域都會比較好;如果重新分析後發現其實這裡僅需要一個加法運算即可,那麼整個邏輯的時空性能将得到進一步提升;等等。

下面,給出一個思路轉換的示例——提前進位多級累加器:

提前進位法提高多級累加器的工作頻率

一般在設計多級累加器時,我們大多将代碼寫成如下形式。以一個3級累加器為例,累加器A的累加範圍為0134,累加器B的累加範圍為070,累加器C的範圍為0~241。

-- VHDL example 
  signal A, nextA, B, nextB, C, nextC : std_logic_vector(7 downto 0); 
  process (clk)
  begin
    if(clk'event and clk = '1')then
      if (rst = '1') then
        A <= X"00";
        B <= X"00";
        C <= X"00";
      else  
        A <= nextA;
        B <= nextB;
        C <= nextC;
      end if;
    end if;
  end process;
  process (A, B, C)
  begin
    nextA <= A;
    nextB <= B;
    nextC <= C;

    if(A = 134)then
      nextA <= X"00";
      if(B = 70)then
        nextB <= X"00";
        if(C = 241)then
          nextC <= X"00";
        else
          nextC <= C + 1;
        end if;
      else
        nextB <= B + 1;
      end if;
    else    
      nextA <= A + 1;
    end if;
  end process;

  // Verilog example
  reg [7:0] A, nextA, B, nextB, C, nextC;
  always@(posedge clk)
  begin
    if(rst)
    begin
      A <= 8'd0;
      B <= 8'd0;
      C <= 8'd0;
    end
    else
    begin   
      A <= nextA;
      B <= nextB;
      C <= nextC;
    end
  end
  always@(A, B, C)
  begin 
    nextA = A;
    nextB = B;
    nextC = C;

    if(A == 8'd134)
    begin
      nextA = 8'd0;
      if(B == 8'70)
      begin
        nextB = 8'd0;
        if(C == 241)
        begin
          nextC = 8'd0;
        end
        else
        begin
          nextC = C + 1'b1;
        end
      end
      else
      begin
        nextB = B + 1'b1;
      end
    end
    else
    begin   
      nextA = A + 1'b1;
    end
  end      

這是一個比較正規的寫法,思路清晰且容易了解,在絕大多數情況下能夠勝任。不過這裡有一個問題,那就是存在一條會成為整個設計瓶頸的關鍵時序路徑。

我們仔細分析一下代碼就會發現,整個代碼隻有三個(其實是三組)寄存器,即A、B、C,它們的值每一個時鐘周期都會更新。

A的下一拍值nextA,僅僅由關于A的組合邏輯決定;

B的下一拍值nextB,由A和B的組合邏輯決定;

C的下一拍值nextC,由A、B和C的組合邏輯決定;

當A等于134,B等于70的時候,輸出一個nextC需要經曆最複雜的組合邏輯,這個邏輯可能是這樣的(不同的編譯器綜合結果會有差異,這裡僅給出一個示意圖):

FPGA之道(59)時空變換之時域優化

從上圖可以看出來,産生nextC所需要的最長組合邏輯時延很可能為1個8位比較器加3個8位2路選擇器的延遲,或者是一個8位加法器和3個8位2路選擇器的延遲。如果考慮了布局布線,那麼情況可能會更加糟糕。并且随着累加數的位寬進一步擴大或者累加級數的進一步增多,這個組合邏輯的時延将會限制到累加器的性能,進而使其不能達到我們所需要的累加計數速度。

為了能夠提高多級累加器的工作頻率,我們需要改變上面這種結構,進而使得當A等于134,B等于70時,nextC不再依賴于如此複雜的組合邏輯。

分析一下A到B的進位規則可以發現,與其在A等于134時才考慮nextB的值,不如在A等于133時就給出一個1位的進位信号,這樣的話當A等于134時,nextB就可以通過判斷這個進位信号來選擇自己的值。于是代碼改寫如下:

-- VHDL example 
signal A, nextA, B, nextB, C, nextC : std_logic_vector(7 downto 0); 
signal carryA, nextCarryA, carryB, nextCarryB, carryC, nextCarryC : std_logic;
process (clk)
begin
  if(clk'event and clk = '1')then
    if (rst = '1') then
      A <= X"00";
      B <= X"00";
      C <= X"00";
      carryA <= '0'; 
      carryB <= '0'; 
      carryC <= '0';
    else  
      A <= nextA;
      B <= nextB;
      C <= nextC;
      carryA <= nextCarryA; 
      carryB <= nextCarryB; 
      carryC <= nextCarryC;
    end if;
  end if;
end process;
process (A, B, C, carryA, carryB, carryC) 
  --following are carrys
  if(A = 133)then
    nextCarryA <= '1';
  else
    nextCarryA <= '0';
  end if;

  if(B = 70)then
    nextCarryB <= '1';
  else
    nextCarryB <= '0';
  end if; 

  if(C = 241)then
    nextCarryC <= '1';
  else
    nextCarryC <= '0';
  end if;

  //following are counts
  if(carryA = '1')then
    nextA <= X"00";
  else
    nextA <= A + '1';
  end if;

  if(carryA = '1')then
    if(carryB = '1')then
      nextB <= X"00";
    else
      nextB <= B + 1;
    end if;
  else
    nextB <= B;
  end if;

  if(carryA = '1' and carryB = '1')then
    if(carryC = '1')then
      nextC <= X"00";
    else
      nextC <= C + 1;
    end if;
  else
    nextC <= C;
  end if;
end process;

// Verilog example
reg [7:0] A, nextA, B, nextB, C, nextC;
reg carryA, nextCarryA, carryB, nextCarryB, carryC, nextCarryC;
always@(posedge clk)
begin
  if(rst)
  begin
    A <= 8'd0;
    B <= 8'd0;
    C <= 8'd0;
    carryA <= 1'b0; 
    carryB <= 1'b0; 
    carryC <= 1'b0;
  end
  else
  begin   
    A <= nextA;
    B <= nextB;
    C <= nextC;
    carryA <= nextCarryA; 
    carryB <= nextCarryB; 
    carryC <= nextCarryC;
  end
end
always@(A, B, C, carryA, carryB, carryC)
begin
  //following are carrys
  if(A == 8'd133)
  begin
    nextCarryA = 1'b1;
  end
  else
  begin
    nextCarryA = 1'b0;
  end

  if(B == 8'd70)
  begin
    nextCarryB = 1'b1;
  end
  else
  begin
    nextCarryB = 1'b0;
  end

  if(C == 8'd241)
  begin
    nextCarryC = 1'b1;
  end
  else
  begin
    nextCarryC = 1'b0;
  end

  //following are counts
  if(carryA == 1'b1)
  begin
    nextA = 8'h0;
  end
  else
  begin
    nextA = A + 1'b1;
  end

  if(carryA == 1'b1)
  begin
    if(carryB == 1'b1)
    begin
      nextB = 8'h0;
    end
    else
    begin
      nextB = B + 1'b1;
    end
  end
  else
  begin
    nextB = B;
  end

  if(carryA == 1'b1 && carryB == 1'b1)
  begin
    if(carryC == 1'b1)
    begin
      nextC = 8'h0;
    end
    else
    begin
      nextC = C + 1'b1;
    end
  end
  else
  begin
    nextC = C;
  end
end      

需要說明的一點是:carryB和carryC的産生原理和carryA不一樣,原因是B和C每變化一次都是多時鐘周期的,是以不能像carryA一樣應用提前進位的方法來産生,不過這并不會拖累多級累加器的性能,因為carryB和carryC都是寄存器,是以它們都起到了阻止前級組合邏輯延遲傳遞到nextB和nextC的作用。

現在我們再仔細分析一下代碼裡的寄存器。

carryA的下一拍值nextCarryA,僅僅由A的組合邏輯決定;

carryB的下一拍值nextCarryB,僅僅由B的組合邏輯決定;

carryC的下一拍值nextCarryC,僅僅由C的組合邏輯決定;

A的下一拍值nextA,由carryA和A的組合邏輯決定;

B的下一拍值nextB,由carryA、carryB和B的組合邏輯決定;

C的下一拍值nextC,由carryA、carryB、carryC和C的組合邏輯決定;

由此可見,nextC仍為最複雜的組合邏輯,但當A等于134,B等于70的時候,nextC的邏輯可能是這樣的(不同的編譯器綜合結果會有差異,這裡僅給出一個示意圖):(&&改為&)

FPGA之道(59)時空變換之時域優化

很明顯,這個nextC的組合邏輯要明顯簡單的多,并且沒有了複雜的比較器,是以,在一個龐大的設計中,這樣的組合邏輯在布局布線後更加容易達到高速的時鐘頻率要求。

布局調整

當編譯器将我們編寫的HDL代碼轉換為門級網表後,編譯過程并沒有結束,隻有将門級網表所代表的内容真正映射到FPGA晶片内部後,FPGA晶片才會具有預期的邏輯功能。是以,一塊組合邏輯的時間延遲不僅僅取決于各個基本組合邏輯單元的門延遲,同時也會受到連接配接各個基本組合邏輯單元的布線的線延遲,有些時候線延遲甚至對組合邏輯的時間延遲影響還要大一些。是以,通過直接調整編譯器布局布線後的結果,人為的将某一塊組合邏輯所包含的各個基本組合邏輯門排列得更緊湊一些,便可以達到提高FPGA設計時間性能的目的。

繼續閱讀