FPGA學習筆記1【FPGA原理與結構】


注意:本篇內容根據《FPGA原理和結構》([日]天野英晴 著 趙謙 譯)一書整理,作者也是初學者,有錯漏請見諒

基礎知識

FPGA即現場可編程門陣列(Field Programmable Gate Arry),與之對應的是FPAA現場可編程模擬陣列(Field Programmable Analog Array)。這是一種可以通過重新編程來實現用戶所需邏輯電路的半導體器件

布爾代數

布爾運算是一種二值運算,在布爾運算中,運算數只有0或1,基本運算有與 ·或 +非 ~三種,可以通過這三種基本運算構造出異或、與非等等運算

布爾運算遵循的定理如下:

  • 零元:x·0=0,x+1=1
  • 單位元:x·1=x,x+0=x
  • 冪等律:x·x=x,x+x=x
  • 補余律:x·(~x)=0,x+(~x)=1
  • 互反律:~(~x)=x
  • 常見的交換律、結合律、分配律
  • 吸收律:x+(x·y)=x,x·(x+y)=x
  • 德摩根定律:~(x+y)=(~x)·(~y),~(x·y)=(~x)+(~y)

布爾代數中的函數用邏輯表達式來描述,描述同一邏輯函數的邏輯表達式可能有多個。邏輯表達式中,邏輯變量以原變量或反變量的形式出現,原變量和反變量統稱字面量,字面量的邏輯與稱為與項,與項的邏輯或運算叫做積之和,包含所有字面量的與項稱為最小項,由最小項構成的積之和稱為標准積之和(標准積),類似的存在或項(字面量的邏輯或)、和之積、最大項、標准和的概念

邏輯函數還能通過真值表描述,針對邏輯函數所有可能的輸入組合一一列出輸出值就可以得到真值表。一個邏輯函數的真值表唯一,實現真值表定義的功能的電路稱為查找表(LUT,Look-up Table),這就是FPGA的基本單元

寫FPGA邏輯就是寫其中的查找表邏輯

數字電路

數字電路也可以叫做邏輯電路

本質是用電路描述數字布爾邏輯。

所有邏輯電路都能用積之和表達式來描述

組合邏輯電路

不包含記憶元件,某時刻輸出(邏輯函數值)僅取決於該時刻輸入的數字電路就是組合邏輯電路

使用與-或-非三種門電路組合成的組合邏輯電路可以實現任何邏輯函數。這種方式被稱為與-或邏輯電路或者是與-或陣列

PLA(可編程邏輯陣列Programmable Logic Array)就是使用與-或陣列

時序邏輯電路

含有記憶元件,某時刻輸出取決於該時刻輸入及系統過去的電路狀態的邏輯電路就是時序邏輯電路

時序邏輯電路分為同步和異步兩種

同步時序電路的輸入狀態和內部狀態的變化由時鍾信號控制同步進行,但異步時序電路不需要時鍾信號

異步時序電路設計需要考慮臨界資源、亞穩態等諸多因素,FPGA很少涉及異步時序電路。一般地,FPGA使用有限狀態機模型來實現同步時序電路,同步狀態機將會在之后的硬件算法部分介紹

同步狀態機分為Moore和Mealy兩類,Mealy模型的狀態數一般比Moore模型的少,電路規模一般也更小,但輸入會立刻反映到輸出,容易造成信號競爭導致非預期的錯誤輸出,這種情況稱為競爭冒險。Moore電路速度快且不易發生冒險,但是電路規模較大

同步電路設計

同步電路讓系統狀態的變化和時鍾信號同步,從而降低電路設計難度

同步電路設計是FPGA設計的基礎

觸發器(Flip Flop,FF)是一種只能存儲一個二進制位的存儲單元,一般用作時序電路的記憶元件。FPGA內一般使用D觸發器(D-FF)在時鍾跳變沿將輸入信號的變化傳送至輸出

它的真值表如下所示

輸入 D 時鍾 CLK 輸出 Q 反相輸出 Q
x 0 保持之前狀態 保持之前狀態
0 上升沿 0 1
1 上升沿 1 0
x 1 保持之前狀態 保持之前狀態
x 下降沿 保持之前狀態 保持之前狀態
無論輸入如何 當且僅當上升沿時 Q<=D Q<=(~D)
任何其他情況 保持之前狀態 保持之前狀態

D觸發器的局限性

CMOS工藝下,D-FF由傳輸門1、主鎖存器、傳輸門2、從鎖存器串聯組成

傳輸們起開關作用,會隨CLK的狀態變化切換開關,外部信號先被鎖存在主鎖存器,一個時鍾周期后,信號會被再次鎖存在從鎖存器。這里傳輸門1和傳輸門2的時鍾相位相反。

然而由於寄生電容、寄生電阻的存在,在時鍾信號變化過快時,如果讀取到還沒有穩定下來而是在0和1之間搖擺的中間電位就會導致讀取錯誤,這被稱為亞穩態。所以一般會通過建立時間(Setup Time)來約束在時鍾上升沿到來前輸入D保持穩定的時間

對於下降沿的情況下,若輸入在傳輸門變為高阻態之前就發生變化也會出現反相器環路震盪的隱患,所以使用保持時間(Hold Time)約束

由於這兩個約束的存在,時鍾頻率需要受到限制,FPGA設計中使用靜態時序分析STA來評估性能,靜態時序分析主要評估FPGA上設計電路的延遲是否滿足時序約束。此外還需要使用DRC約束和DC約束來保證電路的結構無誤

STA驗證具有驗證速度高的特點,但對電路結構有要求:

  • 延遲分析的起點和終點必須是基於同一時鍾的FF,從而能夠通過累加延遲來計算、驗證每條路徑的總延遲

所以FPGA一般來說都會使用單相同步時鍾來設計

由於時鍾信號驅動的負載(扇出數)、布線延遲等導致的時間差稱為時鍾偏移(skew)

由於時鍾振盪器或PLL器件的輸出變形或信號變形導致時鍾邊沿偏離平均位置的情況稱為時鍾抖動(jitter)

在實際ASIC設計中,需要控制時鍾偏移和抖動在一定范圍之內;FPGA上已經提前實現好了多層時鍾樹結構,並通過驅動能力強的專用布線(global buffer)將時鍾低偏移地連接到全芯片的FF上,所以在時鍾設計上比ASIC簡便很多

CPLD

所有可編程邏輯器件統稱PLD,而其中有CPLD(Complex PLD)一個特殊的分支,與FPGA相反,它基於ROM技術制造,基本結構是乘積項。由與陣列和或陣列組成的與-或構造就稱為乘積項形式,這一結構的特點就是成本低廉、掉電后不會丟失邏輯

FPGA簡介

FPGA是PLD的一種。實際上它並不是單純由”門“形成的結構。

FPGA結構簡介

FPGA大致由三部分構成:

  • 實現邏輯電路的邏輯要素:邏輯塊

一般由查找表LUT、乘積項PT、數據選擇器MUX等組成,不管什么方式,都應該是由實現FF等器件的數據存儲電路和數據選擇器MUX組成的

  • 和外部進行信號輸入/輸出的要素:I/O塊

連接IO引腳和內部布線要素的模塊,其中通常包括控制上拉下拉、輸入輸出、極性、速率、開漏等模式的控制電路和觸發器等數據存儲電路。一般會支持TTL、PCI、PCIE、SSTL等等單端標准IO和LVDS等差分標准IO

  • 連接前兩種元素的布線要素:包括布線通道、開關塊SB、連接塊CB

可以通過布線資源形成任意的布線通路,其中主要通過開關的編程配置決定選擇哪個布線通道

實際電路中還可能加入硬核處理器、DSP模塊、塊存儲器等等其他具有固定功能的硬核電路

硬核:具有固定結構、無法使用硬件描述語言編程的電路

軟核:具有一定結構,可以使用硬件描述語言編程的電路

這三個部分一般呈島型排布,每個“島“占據一個格子,格子之間是布線要素

FPGA可編程技術

FPGA通過閃存、反熔絲和靜態存儲器作為可編程的基礎,目前市面上的FPGA多使用SRAM技術(靜態存儲器)制造,這三種技術各有特點

閃存

閃存(FLASH)是EEPROM的一種,屬於非易失存儲器,采用MOSFET(Metal-Oxide-Semiconductor Field Effect Transistor金屬-氧化物-半導體場效應管)技術制造

MOSFET是一種以金屬柵極隔着氧化層利用電場效應來控制半導體的場效應晶體管。而與之相對的還有JFET,即結型場效應管,在N型半導體(這里以N溝道為例)兩端加入P型半導體,並在其中P型半導體較少的一端引出源極和漏極,另一邊的P型半導體引出柵極,用柵極電壓產生的靜電效應來控制漏極電流。MOSFET則是以P型半導體為基底,兩個小塊N型半導體嵌入,通過引腳引出作為源極和漏極,在基底上有一個二氧化硅薄片,上面固定了金屬鋁制造的柵極,當柵極加適當電壓時,在兩個N型半導體之間會出現導電溝道,即源-漏極電流出現(這里以增強型NMOSFET為例,PMOS的導通電流方向相反,耗盡型為不加柵極電壓時才會導通)

無論P類型還是N類型的MOSFET都分為增強型和耗盡型兩種,增強型的特點是“常開”,耗盡型的特點是“常閉”,這里的增強和耗盡指的是柵極電壓控制漏極電流的“增強”或“耗盡”

閃存根據寫入方式不同可以分成兩種:NAND型和NOR型,NAND型在寫入時需要高電壓,而NOR型在寫入時需要大電流

NAND FLASH的MOSFET表現為耗盡型,源極、漏極之間存在一個柵極G,這里就和普通的DRAM構造相同(MOSFET結構),但是在柵極下方的二氧化硅中埋藏了一個多晶硅構成的浮柵。初始狀態下浮柵不帶電,根據耗盡型MOSFET的特點,柵極零偏壓時電流可以通過;但當將柵極加高電壓后,浮柵會由於電場效應(實際是產生了隧道電流)帶電(負電),此時在低電壓范圍內,無論柵極加不加電壓,導電溝道都關閉,電流無法通過,也就是說浮柵上存有電荷時表現為無電流通過;而只有再次在源極S上加高電壓時,電流才能通過。由於浮柵中的電荷沒有逃脫路徑,因此可以半永久保存數據;當在源極加高電壓時,可以將浮柵中的電子以隧道電流的形式引出。對於NOR FLASH的情況,通過在源極和漏極之間通大電流后,一部分電子作為熱電子注入浮柵來完成寫入

NAND FLASH的結構特點導致了它能夠以位(比特)為單位寫入(控制單個柵極電壓),但只能以塊為單位擦除(源極並聯在一起,難以單個控制)

基於閃存的可編程開關

FPGA內的可編程開關由兩個晶體管組成,一個負責編程信號的寫入/擦除,另一個用於控制用戶電路的開關,兩個晶體管共用控制柵極G和浮柵,從編程用開關管注入電子就可以直接決定用戶所使用開關的狀態

使用步驟如下:

  1. 在編程晶體管S-D之間加5V
  2. 在G加-11V電壓,電子自動流入G(G-D電壓為-16V),開關開啟
  3. 正常工作時,柵極保持2.5V,浮柵電位會大致維持在4.5V
  4. 擦除時,編程晶體管S、D接地,G極加16V電壓后浮柵電位會降到0V以下,開關關閉

可編程開關的以上特性決定了它存在一些缺點:

  • 無法使用CMOS工藝(最大硬傷,不過這也導致其成本較低)
  • 重寫次數有限制
  • 接通電阻和負載電容較大

它的優點如下:

  • 非易失、可重編程
  • 對軟性錯誤容錯強、上電后立即工作(Live At Power-Up,LAPU)
  • 尺寸較小(至少比SRAM工藝小,但是對於先進的FinFET優勢就不大了)

目前的CPLD很多都是基於該工藝制造

反熔絲

熔絲是在電流過大時對電路進行保護或防止事故的元件,當有大電流流過時會自行發熱並熔斷。反熔絲的特性和熔絲正好相反:初始狀態為開路,當通電后會發生熔合

一種反熔絲使用多晶硅和n+擴散層作為導體,在他們中甲插入ONO氧化物-氮化物-氧化物的電介質作為絕緣體的構造,ONO電介質厚度在10nm以下,通常在10V-5mA情況下可以形成上下連接的通路。其直徑大約和接觸孔(為了連接硅基底上的門電路和金屬層或上下兩層金屬層而設置的通孔)相當,其導通電阻大概在300~500Ω

還有Metal-to-Metal設計的反熔絲,用於連接布線層,在上下兩層金屬布線層間插入絕緣的非晶硅和鎢插塞等導體,在為編程時處於高阻抗狀態,而在編程處理后可以變為幾乎和金屬層連線同等程度的低阻抗狀態,導通電阻大約50~80Ω。在編程中需要約15mA電流

因為Metal-to-Metal反熔絲面積較小、導通電阻低,構造上不可能將配置信息直接讀取出來,難以實行逆向工程,安全性很高,所以成為現代反熔絲技術的主流

反熔絲優點如下:

  • 尺寸小、密度高、非易失
  • 接通電阻和負載電容小
  • 安全性高、對軟性錯誤容錯性強

缺點:

  • 無法重寫
  • 每根線都需要額外的晶體管用於編程
  • 需要使用專用編程器且編程時間長
  • 難以debug、出錯率較高

靜態存儲器

靜態存儲器由兩個CMOS反相器構成的觸發器和兩個傳輸晶體管(Pass-Transistor,PT)組成。他利用觸發器的雙穩態0和1記錄數據,通過NMOS型的PT執行寫入

靜態存儲器通常使用地址信號來驅動字線(讀取地址信號所控字線上的多位數據),數據的讀取也通過PT進行。由於FPGA需要一直讀取數據,所以在FPGA中數據直接從觸發器讀取而不通過PT。

基於靜態存儲器的FPGA大多使用LUT,並使用MUX等來切換布線連接,真值表本身由多位靜態存儲器構成,這種FPGA一般稱為SRAM型FPGA。說人話就是使用SRAM技術存儲真值表來實現編程信息的存儲

優點如下:

  • 能夠使用先進的CMOS工藝
  • 可重配置
  • 重寫次數沒有限制(一片spartan傳祖孫三輩人沒問題)

缺點:

  • 存儲器尺寸大(可以通過FinFET工藝緩解)
  • 易失性存儲器
  • 難以保證電路信息安全
  • 對軟性錯誤敏感
  • 導通電阻、負載電容較大

目前SRAM型FPGA占據市場主流,因為CMOS和FinFET就是無敵的存在,制程碾壓一切

總結

反熔絲待機耗電低、連接開關的接通電阻小、速度快,並且保密性高、無法進行重寫,比較適用於軍工等不要求最先進工藝但對可靠性要求高的場合

靜態存儲器工藝制程先進、性能強大,但存在易失性且安全性不高、待機功耗大(這一點正在逐漸改善)、耐放射線能力弱,但是很適合民用與實驗,符合FPGA的定位,所以成為市場的主流

閃存工藝是靜態存儲器和反熔絲之間的平衡點,成本低廉且可以重寫、可靠性較高,但是性能較劣勢,說的難聽點就是雞肋,所以市場上基於閃存的FPGA已經越來越少

FPGA的邏輯實現

基於LUT的FPGA邏輯塊可以實現任何輸入數在查找表電路輸入數量之內的邏輯函數;基於乘積項方式實現的CPLD則需要先將表達式轉換為標准積之和的形式

一個小型的電路至少需要通過IO PAD(輸入信號)、FPGA內部布線、可編程邏輯塊、FPGA內部布線、輸出緩沖器、IO PAD(輸出信號)這幾個硬件資源才能實現

可編程邏輯塊內的主要資源就是LUT,LUT是1個字只有1位的內存表,字數取決於地址的位數,FPGA中的查找表存儲單元大多使用SRAM實現。一般k輸入的LUT由2k個SRAM單元和一個2k輸入的數據選擇器組成,查找表的輸入就是內存表的地址信號,輸出就是該地址所選字的1位數據。k輸入的查找表可以實現\(2^{2^k}\)種邏輯函數。

使用查找表時,先根據查找表的輸入值對真值表進行轉換,然后將函數值直接寫入配置內存,當所要實現的邏輯函數的輸入數比查找表的輸入數多時,可以並聯使用多個查找表來實現

查找表的實現

早期FPGA使用過5晶體管結構(兩個晶體管構成一個非門,兩個非門和一個額外的晶體管構成一個SRAM單元)存儲單元,因為LUT中的SRAM總是在輸出數據,所以查找表只需要以5晶體管存儲單元為基礎配合選擇信號就可以實現了;在此之后Xilinx的Freeman改良了LUT的配置存儲器,讓其可以作為FPGA上的分散存儲器使用,此時SRAM具有了獨立的寫入端口,該結構可以復用為存儲器和查找表,使用兩個輸入信號作為控制——需要注意:存儲器的讀取端口和查找表的輸出是通用的

隨后,查找表又被改良可以作為移位寄存器使用;當前的查找表已經能夠支持簇結構(cluster)和自適應(可以將一個8輸入查找表分割為2個7輸入查找表或1個7輸入查找表和2個6輸入查找表等小型查找表簇的方式使用,更加靈活)

FPGA的結構

島型FPGA由邏輯塊、IO塊、布線要素等組成,相鄰的邏輯塊、開關塊、連接塊組成了一個可重復邏輯模塊,模塊呈陣列型排列最終形成島型FPGA

FPGA中具有專門用途的電路稱為“硬核”,可編程的部分稱為“軟核”。Xilinx將邏輯塊稱為CLB(Configurable Logic Block可編輯邏輯塊),Altera則將其稱為LAB(Logic Array Block邏輯陣列塊),但他們的基本原理一樣

除了主要部件之外,現代FPGA通常還包括很多其他部件

邏輯塊結構

對於邏輯塊架構,最重要的設計問題就是權衡邏輯塊的功能和自身面積

集成電路領域著名的AT2定律指的是對於某個給定的半導體工藝流程,如果其相關的某數字設計的面積是A,而執行時間是T,則存在一個下限邊界B,使得AT2=B,也就是說面積和效率不可兼得

這一點同樣適用於FPGA

其中對邏輯塊功能影響最大的是查找表的大小,因為k輸入查找表可以實現任意k輸入的函數,采用較大的查找表有助於減少邏輯塊的使用數量,但k-LUT需要使用2k個配置存儲單元,因此邏輯塊自身的面積會增大,而且增加的輸入/輸出引腳數量會導致布線面積增大。

此外,FPGA運行速度還受到以下影響:如果增加每個邏輯塊的功能,所實現電路的邏輯深度就更小;但同時也會增加邏輯塊自身的內部延遲

邏輯深度指通過關鍵路徑的邏輯塊數量,它由FPGA設計環節中的技術映射過程決定(參考后面FPGA設計與ASIC設計的區別與原理部分),降低邏輯深度可以減少布線、提高電路速度,同時也會增加內部延遲,導致降低邏輯深度的效果大打折扣

綜上所述,查找表的輸入大小和FPGA的面積、延遲有密切關系

現代的商用FPGA都傾向於采用6-LUT

專用進位邏輯

為了提高算術運算電路的性能,現代FPGA邏輯塊中還含有專用的進位電路,可以使用這些專用進位邏輯來獲得更高集成度和運行速度

Xilinx沒有設計專用的全加器電路,而是使用查找表和進位生成電路的組合來實現加法。全加器的加法運算用兩個2輸入EXOR實現,進位輸出電路由1個EXOR和1個MUX組成

可以通過這種單元的不斷組合實現多位加法器甚至加法器矩陣

邏輯簇

邏輯簇是由多個BLE群組化形成的邏輯塊結構,邏輯簇內部局部布線采用硬連線連接,比外部的通用布線速度更快;邏輯簇內部局部布線的負載電容比外部通用布線小很多,因此對FPGA的耗電(特別是動態功耗)的削減有效果;邏輯簇內部BLE可以共享輸入信號,有助於減少局部連接塊的開關數量

含有多BLE的邏輯塊最大的優勢就是在增加邏輯塊功能性的同時又不會大幅影響FPGA的整體面積

邏輯塊面積隨輸入k的增大呈指數級增大;而增加邏輯簇中BLE的數量N會導致邏輯塊的面積只按二次函數增長

計算邏輯塊輸入數量\(I=\frac{k(N+1)}{2}\)

根據上面的經驗公式,邏輯塊面積的增長得到了抑制

一般來說,面積延遲乘積性能最優的結構參數為:N=3~10,k=4~6

也就是說一個邏輯簇中設置3到10個輸入、4到6個BLE比較好

自適應查找表

Xilinx在XC40000系列的邏輯塊中推出了早期的不同輸入數查找表結構

而現代的FPGA多支持將較多輸入的查找表分解使用的機制,也就是自適應查找表

全局布線架構

布線架構分為全局布線和詳細布線,全局布線主要解決邏輯塊的連接、布線通道的寬度等高層次問題;而詳細布線則決定具體的連接方式

層次型FPGA使用UCB的HSRA布線構造,將FPGA內部布線分成多個層次,布線的交叉點上包含各層的開關,一般層次越高通道里連線的數量就越多。目前層次型FPGA已經不再適用

島型FPGA就是一直在介紹的FPGA類型,下面的詳細布線架構也將圍繞島型FPGA說明

詳細布線架構

詳細布線架構中需要確定邏輯塊和布線通道之間的開關布置和布線的線段長度,以此來確定電路延遲

由連接塊和開關塊組成的布線要素對FPGA面積和電路延遲的影響很大,在決定詳細布線架構時要注意:

  1. 邏輯塊和晶體管參數
  2. 布線線段長度的種類和比例
  3. 布線開關的晶體管參數

還應該考慮傳輸晶體管和三態緩沖器使用上的平衡

現代FPGA為了更好的性能,通常使用較多單向連線和輔助的雙向連線,並正在加大單向連線的比重;同時還提供了各種不同長度的連線,從短線到長距離布線,以此增強設備的時序表現

開關塊

所有開關塊均由可編程開關管構成,以下簡稱開關管,注意不要和晶體管意義上的開關管弄混

開關塊的拓撲

開關塊位於橫向和縱向布線通道的交叉處,通過可編程開關來控制布線路徑,一般開關塊的拓撲分為三種

對於一個開關塊中的一個控制晶體管,負責三條交叉路經的通斷,因此開關快都是從三個輸入中選擇一個輸出,自由度Fs=3

不相交型

Xilinx的特色拓撲,因此也被稱為賽靈思型開關塊

由6個開關管構成

將四個方向上序號相同的進行連接,相鄰兩個方向呈斜45度或15度連接

自由度較低

通用型

同樣由6個開關管構成,但兩個成對的連線可以在開關塊內互聯

總體上可實現斜對角45度全連接

但是這種技術只能對應單倍線,無法應用在其他長度的布線上

威爾頓型

采用6*n個開關管連接序號不同的連線

自由度更高,可以實現順時針、逆時針的閉環路徑,可以提高FPGA的測試效率

數據選擇器結構

一般使用4輸入的數據選擇器延遲最優,8輸入的數據選擇器面積延遲乘積最優

連接塊與IO塊

連接塊也由可編程開關構成

連接塊負責連接布線通道和邏輯塊的輸入/輸出

由於需要考慮到連接塊的面積,所以很少使用全交叉開關矩陣實現,而是使用節省掉一些開關的稀疏開關矩陣

IO塊則負責器件的IO引腳和邏輯塊之間的接口部分

一般FPGA的IO除了固定用途的電源、時鍾等專用引腳,還有用戶可以配置的用戶IO,用戶IO具有輸入/輸出緩沖、輸出驅動、信號方向控制、高阻抗控制等功能,和現代MCU的GPIO結構相似

一般具有以下特點:

  • 可配置的上拉、下拉電阻
  • 輸出使能信號可以控制輸出緩沖器
  • 輸入/輸出出啊其,可用於調整信號延遲
  • 可編程的輸出緩存器轉換速率
  • 可以調整使用TTL、CMOS、PCIE等多種輸出電壓標准或輸入緩沖器閾值
  • 專用延時電路,用於保證輸入的保持時間
  • 可適用於高速通信的差分信號LVDS
  • 配置有鉗位二極管

總體而言連接塊與IO塊類似MCU中的總線橋和GPIO

嵌入式硬核

DSP塊

現代FPGA被普遍用於數字信號處理DSP、人工智能加速AIA、硬件算法實現等場景,因此出現了搭載大量乘法器、浮點運算電路的FPGA

這些內嵌DSP塊的結構大致如下:

輸入

前置加法器

乘法器

累加運算單元

模式檢測器與控制電路

輸出

很多DSP塊支持粒度調節和浮點運算支持

在FPGA廠商提供的IP生成工具中設置好相關IP就可以選擇是否在DSP塊上實現,同時使用FPGA廠商推薦的硬件描述語言使用方式,綜合工具就可以自動識別並使用DSP塊,也可以在代碼中直接實例化並使用DSP塊的模塊,但這種方法缺乏可移植性

硬宏

硬宏:商用FPGA中嵌入的專用硬件電路

硬件乘法器和DSP塊都屬於硬宏,但都比較常見,當前已經被視為商用FPGA的“標配”

除此之外,很多中高端產品都會搭載PCIE接口、USB接口、SPI接口、外部DRAM接口、專用DAC、ADC等硬宏

一般來說硬宏化的接口電路不多,需要考慮硬宏的位置再進行布局布線

硬核處理器

FPGA廠商已經越來越多地在高端品牌上搭載硬核處理器,也就是作為硬宏的嵌入式處理器

比較著名的就是Xilinx的Zynq系列,搭載ARM Cortex A系列處理器,可以運行Linux等通用OS或RT-Thread等大型RTOS,同時可以按照標准設計電路接口,讓FPGA部分的用戶電路與硬核處理器通過AMBA交換模塊連接,以定制硬件的方式實現加速

嵌入式存儲器

部分商用FPGA為了解決存儲器總線帶寬問題,直接將存儲器嵌入到FPGA內部

這樣的嵌入式存儲器分為兩種類型

  1. 存儲器塊硬宏

    以硬宏的形式在架構中嵌入存儲器塊,在Xilinx的架構中,這種存儲器被稱為塊存儲器(Block RAM,BRAM),支持一分多、多合一

    BRAM既可以作為單端口存儲器也可以作為雙端口存儲器使用,可以方便的使用它作為FIFO存儲器

    需要注意:BRAM不支持異步訪問,想要同步兩個時鍾域還是應該使用傳統的異步時鍾FIFO

  2. 查找表存儲器

    也可以使用邏輯塊內部的查找表實現存儲器

    Xilinx的架構中,這種由查找表構成的存儲器稱為分布式RAM(distributed RAM),但只有被稱為SLICEM的邏輯塊中的查找表才能夠作為分布式RAM使用。此外,為了不擠占查找表的存儲資源,一般在需要小規模存儲器時再使用這種方法

嵌入式存儲器一般和DSP塊一樣,只要按照FPGA廠商的說明編寫HDL程序,綜合后就能夠調用

它的優點在於大,能有效節省外部總線資源和時序損耗

配置鏈

把電路編程到FPGA上的過程叫做配置,向FPGA寫入的電路信息叫做配置數據。配置數據包含在FPGA上實現電路的所有信息,包括但不僅限於查找表中的真值表數據和開關塊中各個開關的狀態等

一般地,FPGA可以分為三種類型

  • SRAM型:掉電信息即丟失,所以一般在上電時使用內部或外部的自動配置器件將配置信息寫入
  • 閃存型:非易失,一般閃存寫入次數的限制都比較大,雖然沒有SRAM那樣的永久壽命,但用十幾年也沒有問題,只是寫入速度較慢
  • 反熔絲型:寫入一次后就不能再修改

一般在調試過程中使用JTAG接口,可以實時觀測FPGA內部信號的變化:先將觀測信號的變化寫入嵌入式存儲器,再通過JTAG讀取到上位機,這樣就能直觀地看到信號波形,稱為虛擬邏輯分析儀

這樣對於配置流程就能有更好的把控

PLL與MMCM

過去的FPGA一般使用外部晶振,在內部的時鍾信號頻率受到限制

現代FPGA大多配置有PLL電路,可以將外部輸入的基准時鍾進行進一步處理

PLL即Phase Locked Loop鎖相環,外部基准時鍾輸入鑒相器,鑒相器就是一個精密的時鍾相位比較器,將PLL內部壓控振盪器(Votage-Controlled Oscillator,VCO,可以根據所加的電壓調整輸出頻率)生成的時鍾與外部輸入的基准時鍾進行比較,如果兩個時鍾一致則維持VCO電壓,否則調整控制電路對VCO電壓進行調整(VCO主頻過高則降低電壓;反之升高電壓),經過鑒相器得到的電壓還存在一定的高頻諧波,通過一個低通濾波器后輸入VCO即可實現效果。整個電路呈現為

基准時鍾輸入-鑒相器-低通濾波器-VCO-反饋輸入-標准輸出

的狀態。整個電路使用模擬電路實現,高頻諧波由於反饋電路和后級電路耦合而產生

graph LR A[輸入]-->B(鑒相器) C[反饋]-->B B-->D[低通濾波器] D-->E[VCO] E-->C E-->F[輸出]

除了基本結構之外,PLL通常還會在輸入輸出階段添加額外的分頻器用於削減輸出頻率;同時在反饋階段加入分頻器用於增加輸出頻率

流程框圖如下:

graph LR INPUT(輸入)-->A[基准時鍾預分頻器N] A-->B(鑒相器) C[反饋時鍾分頻器M]-->B D-->E[VCO] E-->C B-->D[低通濾波器] E-->F[輸出時鍾分頻器1] F-->OUTPUTA(輸出時鍾1) E-->G[輸出時鍾分頻器2] G-->OUTPUTB(輸出時鍾2) E-->H[輸出時鍾分頻器i] H-->OUTPUTC(輸出時鍾i)

可以得到輸出頻率控制公式

\[F_{vco}=\frac{M}{N \cdot K_i} F_{ref} \]

式中M為反饋時鍾分頻器分頻系數,N為基准時鍾預分頻器分頻系數,Ki為輸出時鍾分頻器分頻系數,最后對應輸出時鍾Fi

雖然可以通過有限個預分頻器調節時鍾,但事實上各個分頻器的分頻比是有限制的,設定值不能超出規定

PLL實質上是一個以外部輸入基准信號為目標,使用VCO為控制對象的線性反饋控制系統,在啟動、復位或基准時鍾大幅度變動時,無法做到立即響應,所以在PLL的輸出時鍾穩定前,由該時鍾同步的用戶電路可能會發生無法預測的動作。為了避免這種情況,常常使用PLL鎖定機制,PLL通常會設置1位輸出信號來表示PLL是否為鎖定狀態,外部電路可以利用該寄存器位來判斷是否能使用時鍾

使用數字方式也可以實現PLL類似的功能,稱為DLL,具有響應速度快的優勢。但是因為PLL的自由度更高、穩定性更好、成本更低,所以大多使用PLL或其升級版本MMCM

FPGA設計流程與原理

基於HDL

  1. 使用FPGA廠商提供IDE創建工程

  2. 約束設定

    設置物理約束、引腳約束

  3. 創建源文件

    依次編寫RTL級描述,可以按照自頂向下/自底向上設計方法,先編寫頂層文件(系統級)或底層模塊;通過模塊例化進行層次連接

  4. 創建仿真源文件

    可以使用SystemVerilog等語言創建仿真用的源文件testbench

  5. 邏輯綜合和技術映射

    對於Xilinx系FPGA來說需要先進行RTL文件的邏輯綜合,如果使用了Verilog HDL則會先生成門級Verilog文件,隨后生成網表文件,這些網表文件描述了邏輯門、觸發器等邏輯元素的集合以及它們的連接關系。綜合后在進行技術映射,將邏輯映射到FPGA實際的邏輯元素。

    一般在執行這兩步時,廠商IDE會提供DRC、網表、邏輯分析報告,可以借此初步判斷bug

  6. RTL仿真

    使用testbench對電路進行仿真,這一步可以使用廠商提供的仿真軟件或仿真器,也可以使用集成電路設計中常用的modelsim等仿真軟件。通過對輸入輸出波形的仿真判斷電路是否存在問題

  7. 布局布線

    利用片上邏輯和布線等資源實現網表。先進行邏輯元素再進行網絡布線。布局過程中可以對信號擁擠度和傳輸延遲進行初步預測,不過並不一定准確,目前業界多使用STA靜態時序分析進行仿真。需要特別注意的是,布局布線需要耗費很長時間,規模越大、邏輯資源使用率越高的電路耗時越長,且布線失敗的可能性越大。當前的IDE中雖然有自動布局布線功能,但是也可能發生問題,這就需要換用硬件或重新設計架構、算法等

  8. 配置FPGA

    可以通過以下方法將編程數據燒錄到FPGA:

    • JTAG燒錄比特流文件

      JTAG是面向器件編程和板卡調試的一種通用標准,FPGA廠商都提供了對應FPGA的JTAG燒錄器/調試器

      但是JTAG燒錄后如果FPGA斷電或重置,配置信息就會丟失

      一般使用bit格式

    • 自動從片外ROM中讀取文件

      在上電或重置時可以讓特殊配置過的FPGA從片外ROM中讀取配置數據或使用其他控制器將片外文件數據加載到FPGA上

      一般使用mcs或pof格式

    • 通過儲存卡等寫入

    • 使用專用IC或總線協議配置FPGA

    特別地,一些FPGA配備了處理器硬核,可以使用SD卡或專用總線,可以用這種方式自動加載FPGA

  9. 實機功能驗證

  10. 優化

    在實現功能后,可以考慮更改設計來讓器件達到更高的工作頻率或獲得更高的效率

基於HLS

使用FPGA廠商專用的HLS工具可以將高級語言代碼轉換為HDL代碼,進而實現方便的電路設計

詳細內容可以查看其它關於HLS的介紹

基於已有IP

往往可以從廠商或開源社區獲取到某些器件的RTL代碼並加以修改融入當前設計

使用這種開發方式可以

  • 使用開源的處理器RTL代碼實現軟核處理器
  • 構建SoC
  • 整合軟硬件開發

FPGA設計與ASIC設計的區別與原理

FPGA設計自由度較低,無法實現對硬件的定制,且無法在器件內集成模擬集成電路元件,但成本較低;而ASIC可以有更高的自由度,但成本高昂;FPGA也常常被用於ASIC設計中的原型驗證

除此之外,FPGA與ASIC設計的不同點還體現在如下方面

工藝映射

工藝映射:將不依賴於任何工藝的門級網表轉換為由特定FPGA邏輯單元所表示的網表的過程

人話:把網表翻譯成用FPGA內部邏輯單元組成的電路

ASIC設計中並不需要這一步,而是直接將綜合后的RTL文件映射為器件,之后就是版圖設計師的任務了

工藝映射的步驟有兩個:

分解

門級網絡實際上是用布爾網絡(基於有向圖DAG的門級網表的表現方式,各個節點表示邏輯門或邏輯門的組合邏輯,有向邊則表示輸入/輸出信號)的形式來表示的,在這一步里要將布爾網絡的各個節點分解直到輸入數小於查找表的輸入數k

覆蓋

使用某種基准對分解得到的布爾網絡進行切分,使用k-LUT覆蓋多個節點

實際上這個過程是一種可以在多項式時間內找到邏輯層數最優解的方法[1]

一種執行覆蓋的方法FlowMap如下:

  1. 獲取分解得到的最小電路
  2. 獲取輸出信號對應的最后一個器件,該器件被抽象為有2個輸入、1個輸出的節點
  3. 將所有輸入節點標號為0
  4. 從輸出節點開始,以遍歷樹的方式尋找對應的上一層節點,將其輸入切分出來
  5. 在上一層中標注標簽最大的數字,再加上當前節點的層數(以最后一層的遍歷為例,上一層標簽為0+1=1)
  6. 順序計算已經標注節點的相鄰節點,如果遇到還未標注的節點,先對其進行計算標注
  7. 在當前層(切分點以下)所有相關標簽都計算完畢后,再計算第二層節點的標簽
  8. 反復計算所有結點的標簽
  9. 每一層的邏輯都可以讓單個查找表實現
  10. 執行邏輯打包

術語介紹:

  • 節點(node):使用布爾網絡表示DAG時基於2輸入邏輯門模型表示的基本構成要素——電路網絡中的邏輯門全部用2輸入1輸出的節點來建模表示

  • 標簽:標簽的數值用於表示網絡的深度,即從各個節點到主輸入按照最小深度映射時的邏輯層數

  • 切分集:按照k輸入進行工藝映射時可能實現的切分集合

    切分:將節點划分為總輸入在k之內、可以使用查找表實現的節點集合

邏輯打包

邏輯打包:將多個查找表和觸發器集合到同一個邏輯塊的過程

目前主流的FPGA邏輯塊都有多個查找表,所以需要高效地將查找表打包到邏輯塊

邏輯打包的要點如下:

  1. 邏輯塊內部布線(局部布線)和邏輯塊外部布線(布線通道)的延遲相差較大,所以需要平衡並縮短延遲
  2. 如果邏輯塊中有查找表空閑,資源使用率就會降低,所以需要增加邏輯塊使用量

一種早期的執行邏輯打包的方法[2]如下:

  1. 選擇輸入占用最多的LUT作為邏輯塊的種子
  2. 將具有最多共同輸入信號的LUT裝填到當前邏輯塊

這種方法無法考慮邏輯布線的延時差,所以有以下改進版本[3]

采用Timing-driven(延時驅動)的裝箱算法T-VPack

  1. 選擇關鍵路徑上輸入最多的LUT作為邏輯塊的種子

  2. 考慮連接重要度和影響路徑數

    連接重要度使用slack(延遲余裕)值計算而來,用於判斷當前路徑對時序影響的大小和是否是關鍵路徑

    slack值越小,該路徑就越接近關鍵路徑

    影響路徑數指的是當前LUT所影響的關鍵路徑的數目,即輸入和當前LUT之間關鍵路徑的總和,該指標表明了當前查找表的延遲一旦改善,總共會有多少路徑可以隨之得到改善

  3. 考慮共同信號數量

  4. 連接重要度大、延遲余裕小、共同信號數量多的邏輯打包到同一個查找表

這種算法很好的考慮到布線延遲和邏輯塊使用量的均衡

近年來的自適應查找表也對裝箱算法有很大影響

較為現代化的算法有AAPack等

布局布線

布局:決定邏輯塊的物理位置

布線:決定邏輯塊的信號連接路徑

多數FPGA的邏輯塊都成二維陣列狀排列,因此邏輯塊布局問題可以被視為二次分配問題,這類問題也被公認為NP問題(具有和計算復雜度理論中的NP(Non-deterministic Polynomial Time,非確定性多項式時間)類問題同等或同等以上難度的問題,二次分配問題是具有NP難度的組合優化問題中非常難解的一種問題),通常只能使用SA(Simulated Annealing)等算法獲取近似解

布線過程中主要使用兩種布線方法:全局布線和詳細布線

全局布線主要決定線網的布線路徑;詳細布線則基於全局布線所得信息確定路徑具體使用了哪些布線資源、通過了哪些開關等

這一步FPGA開發和ASIC設計采用的思路都差不多:瞎蒙

具體來說使用VPR工具步驟如下:

  1. 隨機放置邏輯塊、IO塊
  2. 計算當前布局的布線擁擠度
  3. 隨機選擇兩個邏輯塊並對調位置
  4. 比較對調后的布線擁擠度
  5. 比較對調前后擁擠度的數指,決定是否接受新的布局

硬件算法簡介

對於FPGA,或者說ASIC而言,對比CPU最大的優勢就是可以實現並行計算,同時能夠針對特定問題、特定算法進行優化

流水線

將一個連續的硬件電路拆分為n個均等的階段,每個階段的輸出作為下一個階段的輸入,下一個階段執行必須等待前一個階段完成,每個階段稱為流水線的“級”。對於一個非流水線結構中的運算,完成時間為L,則加入n級流水線后,每L/n個單位時間就可以完成1個運算,這樣就將同時在電路運算中的數據量增大了n倍,即吞吐量增大了n倍。

實際運算中,n級流水線並不一定能夠得到n倍的速度提升

對於n級流水線結構,完成N個運算所需時間為

\[T_{pipe}(N)=L+\frac{(N-1)L}{n}=\frac{n+N-1}{n} \]

其中完成一個運算所需時間為L

速度提升率為

\[S_{pipe}(N)=\frac{T(N)}{T_{pipe}(N)}=\frac{nN}{n+N-1}=\frac{n}{1+\frac{n-1}{N}} \]

\(n\ll N\)時,有\(S_{pipe}(N)\cong n\)

流水線結構和非流水線結構相比所得的速度提升和級數成正比,系數為n

這里要注意的是:全部N個運算的時間被縮短,但每個運算從剛開始到結束的時間沒有變化,且在剛開始計算時,也就是流水線沒有完全開始工作時的載入過程,還有結束計算時的清空過程無法省略且難以獲得吞吐量增益。

流水線結構設計中,還要注意時鍾周期限制,在寄存器與寄存器之間存在傳輸延遲、關鍵電路延遲。當將非流水線硬件改為流水線結構設計時,需要考慮各個部分的延遲。考慮不周很可能發生前一級完成傳輸時后一級的傳輸還在進行,就會產生電路錯誤

對於兩個寄存器之間的延遲,\(時鍾周期>傳輸延遲+組合邏輯電路的關鍵電路延遲+建立時間\),這一時鍾周期對應頻率的極限就是電路時鍾頻率的最大值

當級數增多時,流水線寄存器延遲、建立時間、寄存器輸入時鍾信號偏移、各級之間處理延遲的不同導致的問題越發顯著,雖然引入流水線可以在一定程度上增加總體處理速率,增大吞吐量,但是會引入更多需要考慮的因素

並行計算與Flynn

Michel.J.Flynn在1965年提出了稱為Flynn分類的並行計算架構分類方法

在通用計算機架構中存在用於控制的指令流和作為運算對象的數據流,Flynn根據基於指令流和數據流的並行度對架構進行分類,分成SISD、SIMD、MISD、MIMD四類,將計算機模型抽象為運算單元PU、控制單元CU、數據存儲器、指令存儲器四個部分

  • SISD架構

    單一CU從指令存儲器讀取指令流來控制單一的PU,PU受CU控制,從數據存儲器讀取單一數據流進行計算處理

    這一架構代表基本的順序計算,不具備並行計算能力

  • SIMD架構

    單一CU讀取指令流的同時控制多個PU,各個PU接受相同的控制,各自對不同的數據流進行相同的計算處理,各個PU可以有自己的本地存儲器,也可以所有PU訪問同一個共享存儲器

    該架構中的PU常常被用於圖像處理等ASIC中

    通常處理器還會提供SIMD指令來實現數據並行計算,經典的浮點數SIMD指令就是其中之一

  • MISD架構

    多個CU各自讀取不同的指令流並控制多個PU,各個PU根據不同的控制指令對單一數據流進行操作,操作完成后前一階段的PU將計算結果交付給下一階段的PU,這樣就形成了類似流水線的結構。通過每個CU各自控制一個不同功能的PU來實現並行計算,一般和SIMD架構同時使用來提高效率

  • MIMD架構

    多個CU各自讀取不同的指令流並控制多個PU,各個獨立受控的PU對不同的數據流並行處理

    該架構通常應用於具有SMP等多個核心緊密結合的處理器,可以實現共享數據存儲器或獨立存儲器的計算

脈動算法

脈動算法:基於H.T.Kung所提倡的脈動陣列所實現並行處理算法的簡稱

脈動陣列:由大量單一或多種構造的運算元件(PE)按規律排列的硬件架構,其中只有相鄰的運算元件互相連接,運算元件只重復進行簡單的數據處理和必要的數據收發,且由統一的時鍾同步工作,數據每次只能在相鄰運算原件之間移動;使用了總線等連接方式的架構稱為半脈動陣列

脈動陣列中的運算元件也可以稱為單元

這種架構的系統性能可以隨陣列規模擴大而成比例增加,非常適合在集成電路上實現

基於一維脈動陣列的部分排序

排序:將數據按照某種順序重新排列的過程

一維排列上設計N個具有寄存器的PE,用於對N個數據進行排序

PE的寄存器用於保存臨時最大值XMAX,當輸入比臨時最大值大時將臨時最大值更新為輸入值(使用兩路數據選擇器,輸出較大的數據),在PE之間不斷重復這個過程直到所有N個數據進入PE,數值最大的N個數據就會依次存儲在各個PE的寄存器中,最后一起輸出就完成了比較步驟

一般的硬件排序電路一般還具有_rst復位輸入、mode模式選擇輸入、shiftRead從大到小逐次讀出等輸入端

這樣的電路一般也可以當作移位寄存器

基於一維和二維脈動陣列的矩陣向量相乘

矩陣向量相乘Y=AX的計算也可以采用一維脈動陣列實現,呈網格狀排列的二位脈動陣列可以更直觀地體現計算思路

  • 一維

    需要N個PE來實現N x N的矩陣運算,每個PE都是獨立的加法器,矩陣X和A分別從左和從上輸入陣列,PE會將兩矩陣的對應行、列元素相加,並將結果暫存在各自的寄存器中。每個時鍾周期都會進行運算並輸出當前寄存器內的值

    需要注意在運算開始前應將寄存器初始化為0,即實現如下算法(C語言形式)

    y=0;
    

    每一步都會執行如下算法(C語言形式)

    y=y+a*x;
    

    最后完成運算所需總步數為2N-1

  • 二維

    二維矩陣只需要將兩個輸入矩陣錯位,每個時鍾周期輸入一次即可

    運算所需總步數為3N-2

實現任意模板計算的可編程脈動陣列

  • 運算器

    用於進行浮點數乘法和加法

  • 本地存儲器

    用於在脈動陣列電路內存儲一部分運算數據

  • 交換電路

    將當前位置的運算器和本地存儲器中的數據運輸到其他地方

  • 可編程序列發生器

    控制整個脈動陣列,使用微程序進行操縱

組成

通常一個脈動計算存儲器陣列分為多個控制組,各控制組內的PE由統一序列發生器控制,按照SIMD架構並行處理

結合微程序控制交換電路對數據進行搬運,就可以實現任意模板計算

數據流機

數據流機是一種只要輸入數據就能進行計算的非馮諾依曼架構的計算機。這種運行方式被稱為數據驅動方式

他將對象程序轉化為數據流圖后執行處理。一個典型的數據流機有以下節點:

  • Fork復制數據到另一支路
  • Primitive Operation按描述進行兩個數據的算術運算並輸出結果
  • Branch控制數據流向多個分支中的哪一個分支
  • Merge根據條件信號的值選擇輸入數據並輸出
  • 令牌:被操作的數據

數據流機可以簡單地實現條件分支和循環

其中條件分支可以使用Branch和Merge的組合來實現,而循環則有兩類實現方式

靜態數據流機

將循環完全展開,全部以數據流的形式實現

優點:並行性很高

缺點:數據流圖的規模龐大,對應的電路結構也無法避免地變大

常用於節點運算功能和運算數混合存在的場合,電路中一個命令單元對應一個運算器,運算器會通過控制網絡向命令單元傳輸當前狀態。處理完畢的數據進入分發網絡送至命令單元,命令通過仲裁網絡以操作包的形式發送到運算器,這樣就實現了循環的控制

動態數據流機

只實現循環體的數據流,在之后的循環中復用同一組硬件搭配條件分支來實現

優點:

缺點:需要設置額外的控制電路,如果發生循環間令牌混亂的情況就難以保證計算的正確性

常用於節點運算功能和運算數分離的場合,可以使用帶標號的令牌來實現循環處理

典型的電路結構如下:

其中每個PE的結構如下:

運算數據由標號令牌表示,程序和數據完全分離,每個PE都可以視作一個小型的馮諾依曼架構計算機,數據令牌則由存儲位置指令的狀態編號、自己的標號、存儲位置指令的入口編號、存儲位置指令的運算數組成,控制不同標號的運算數據進入不同的PE即可實現循環

PE中的I結構是一個為數組等簡單數據結構提供等待功能的模塊,在按數據驅動方式處理數組訪問時,需要保證數據在寫入之后再次被讀取。一般的解決方法是對每一個元素設置一個存在標志位,指示數據是否已經被寫入,I結構一般要內置該功能以加速簡單數據結構的運算。

Petri網

表示信號輸入/輸出的圖被稱為Petri網,信號轉換圖是它的一個子類。可以用於描述並行系統和異步系統

Petri網是由庫所(place)和變遷(transition)兩類節點和有向弧組成的二分圖

系統的狀態或條件由庫所表示,系統狀態遷移的發生和完成等事件用變遷表示,庫所->變遷的有向弧表示現象的發生及其前提條件,變遷->庫所表示事件發生后的狀態和成立條件的關系。

Petri網可以表示以下基本狀態行為:

  • 並發
  • 沖突
  • 困惑
  • 同步
  • 資源共享
  • 讀取
  • 有限容量

常使用Petri網和數據流機共同描述一個算法,以便在HDL中進行有效的算法優化

流處理思想及其實現

流處理:針對逐個輸入的數據序列,持續依次處理其中各個元素的方式

數據元素可以是單一標量數據,也可以是包含多個字的向量數據,但流處理每次只能處理一個元素,這就導致元素增多(數據流增長)時的處理時間會成比例增加,不過這也相當於只要付出時間就可以處理巨大的數據集

流處理數據元素的處理單元稱為處理核(kernal),流處理中可以只包含一個處理核也可以包含多個處理核

硬件實現

如果有充足的硬件資源,可以實現多個處理核,每個處理核作為一級流水線相互連接,最終形成一個大型流水線設計,這樣的系統能夠達到吞吐量為1的流處理,每個時鍾周期都可以進行數據的輸入/輸出;當硬件資源不充足時,可以將原數據流圖折疊變小后映射到硬件,這種方法稱為折疊法,該設計會導致處理周期增加,吞吐量降低,但可以節省硬件資源或在硬件資源有限的情況下實現流處理

對於某些硬件使用率不到100%的電路,也可以考慮使用折疊法來提高硬件使用率:在運算器輸入處插入數據選擇器,在需要提高硬件使用率的情況下將正常的流處理程序改變為折疊過后的流處理程序;如果連續處理的多個數據之間存在依賴關系,可以在流處理過程中插入延遲緩沖存儲器,將上一步運算的結果暫存,在下一個時鍾周期進行處理來實現流處理效果

元胞自動機

元胞自動機是基於網格狀單元核簡單規則的離散計算模型,於馮諾依曼等人在20世紀40年代提出。

元胞自動機由具有有限個狀態的元胞組成,經過離散時間后每個細胞的狀態都會發生變化,某時刻t元胞的狀態會受到上一個時刻元胞狀態和其鄰居元胞狀態的影響。按照考慮鄰居元胞的個數,分為馮諾依曼型(只考慮上下左右四個元胞的狀態)和摩爾型(考慮全部八個周邊元胞狀態)

最著名的元胞自動機就是生命游戲,它的一種簡單C語言實現如下:

//簡單的狀態機練習
#include "LifeGame.h"

//開始菜單
int CtrlGame(void)
{
    char input=0;
    printf("Welcome to play LifeGame!\n");
    printf("\n\n\n");
    printf("Enter p to start\n");

    while(1)
    {
        input=getch();
        if(input=='p')
            return 1;
        else
            return 0;
    }
}

//游戲數據初始化
void InitGame(void)
{
    srand((unsigned)time(NULL)); //生成種子
    for(int i=0;i<HIGH;i++)
    {
        for(int j=0;j<WIDTH;j++)
            cell_map[i][j]=rand()%2;//細胞初始狀態隨機
    }
}

//進行游戲
void RunGame(void)
{
    int cell_num=0;

    while(1)
    {
        system("cls");//清屏

        for(int i=0;i<HIGH;i++) //打印本迭代細胞地圖
        {
            for(int j=0;j<WIDTH;j++)
            {
                if(cell_map[i][j]==1)
                    printf(" # ");
                else if(cell_map[i][j]==0)
                    printf(" * ");
            }
            printf("\n");
        }

        //計算1個細胞周圍8個格子內的活細胞總量並進行下一步判斷
        for(int i=0;i<HIGH;i++)
        {
            for(int j=0;j<WIDTH;j++)
            {
                if(i==0) //最上一行
                {
                    if(j==0) //左上角
                    {
                        cell_num=
                                                         cell_map[i][j+1]+
                                      cell_map[i+1][j]+ cell_map[i+1][j+1];
                    }
                    else if(j==WIDTH-1) //右上角
                    {
                        cell_num=
                                     cell_map[i][j-1]+                     
                                     cell_map[i+1][j-1]+ cell_map[i+1][j];
                    }
                    else //其他
                    {
                        cell_num=
                                     cell_map[i][j-1]+                     cell_map[i][j+1]+
                                     cell_map[i+1][j-1]+ cell_map[i+1][j]+ cell_map[i+1][j+1];
                    }
                }
                else if(i==HIGH-1) //最下一行
                {
                    if(j==0) //左下角
                    {
                        cell_num= cell_map[i-1][j]+ cell_map[i-1][j+1]+
                                                    cell_map[i][j+1];
                    }
                    else if(j==WIDTH-1) //右下角
                    {
                        cell_num=cell_map[i-1][j-1]+ cell_map[i-1][j]+ 
                                 cell_map[i][j-1];
                    }
                    else //其他
                    {
                        cell_num=cell_map[i-1][j-1]+ cell_map[i-1][j]+ cell_map[i-1][j+1]+
                                 cell_map[i][j-1]+                     cell_map[i][j+1];
                    }
                }
                else if(j==0) //除左上角和左下角的最左一列
                {
                    cell_num=cell_map[i-1][j]+ cell_map[i-1][j+1]+
                                               cell_map[i][j+1]+
                             cell_map[i+1][j]+ cell_map[i+1][j+1];
                }
                else if(j==WIDTH-1) //除右上角和右下角的最右一列
                {
                    cell_num=cell_map[i-1][j-1]+ cell_map[i-1][j]+
                             cell_map[i][j-1]+
                             cell_map[i+1][j-1]+ cell_map[i+1][j];
                }
                else //其他位置
                {
                    cell_num=cell_map[i-1][j-1]+ cell_map[i-1][j]+ cell_map[i-1][j+1]+
                             cell_map[i][j-1]+                     cell_map[i][j+1]+
                             cell_map[i+1][j-1]+ cell_map[i+1][j]+ cell_map[i+1][j+1];
                }

                if(cell_num==3) //當周圍有3個活細胞時,該細胞下一代為活細胞
                    new_cell_map[i][j]=1;
                else if(cell_num==2) //周圍有2個活細胞時,該細胞下一代狀態不變
                    new_cell_map[i][j]=cell_map[i][j];
                else //其它情況時,該細胞下一代轉為死細胞
                    new_cell_map[i][j]=0;
            }
        }

        for(int i=0;i<HIGH;i++) //創建下一迭代
        {
            for(int j=0;j<WIDTH;j++)
                cell_map[i][j]=new_cell_map[i][j];
        }

        Sleep(1000);
    } 
}
 
int main(void)
{
    if(CtrlGame()==1)
    {
        InitGame();
        RunGame();
    }
    return 0;
}

LifeGame.h

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "windows.h"

//游戲尺寸
#define HIGH 10
#define WIDTH 10

int cell_map[HIGH][WIDTH]; //初始狀態細胞地圖
int new_cell_map[HIGH][WIDTH]; //下一代細胞地圖

int CtrlGame(void);//開始菜單
void InitGame(void);//游戲數據初始化
void RunLifeGame(void);//進行游戲

硬件排序算法

排序:將由n個元素組成的亂序數列按升序或降序重新排列的過程

硬件上常常使用排序網絡歸並排序樹

基於冒泡排序的排序網絡

特點:可以並行地對相鄰兩個元素進行排序

排序網絡由連線和用於排序相鄰元素的交換單元EU組成

連線的數量和元素的數量一致,每個元素最多通過n-1級EU,使用流水線可以提升吞吐量

缺點:面積大,占用硬件資源

在此基礎上換用Batcher奇偶排序網絡可實現高效排序

歸並排序樹

使用二叉樹結構連接各個EU,使用FIFO進行輸入/輸出實現電路內部和外部去耦

全部EU的排序處理可以並行執行,待排列的數列並行輸入歸並排序樹,每層EU選出兩個元素中較大(較小)的那個,每層EU將排序后的數列送到下一層的輸入FIFO,依次執行最后可以串行輸出排序后的數列

模式匹配

模式匹配:在數據中按照給定的模式進行搜索

精確匹配

精確匹配中的模式長度固定,分成0、1、’dont care‘三種狀態

可以使用CAM(Content Addressable Memory)存儲器實現。在FPGA上常常實現CAM的索引生成單元(Index Generation Unit,IGU)

總體思路是將輸入數據p存儲到主存儲器,主存儲器輸出索引生成函數f,使用輔助存儲器存儲f,將其和匹配輸入X進行比較,輸出q,使用q在輔助存儲器查詢得到X’,將X和X‘進行邏輯與運算,如果兩者相同就輸出q,否則輸出0

IGU可以將2n個元素的集合映射到k+1個集合,內存占用量從O(2n)縮減到O(2p),其中p是輸入數據p的位數

正則表達式匹配

正則表達式由字符和描述字符集合的元字符組成。正則表達式匹配等價於使用同等功能的有窮自動機對字符串進行處理

對於確定的輸入無法確定遷移狀態的自動機稱為非確定性有窮自動機(NFA);對確定的輸入能夠確定遷移狀態的自動機稱為確定性有窮自動機(DFA)。采用DFA的硬件加速實現一般基於Aho-Corasick算法;采用NFA的硬件加速一般使用Prasanna方法

下面僅介紹NFA實現:

  • 電路使用存儲器對單個字符進行檢索,並將結果送入匹配單元ME
  • ME會模擬狀態遷移過程並輸出匹配信號

總體上是利用一個解析元字符的ME來控制對整個字符串的檢索

NFA和DFA兩種並行硬件的比較如下

按位分割DFA Prasanna-NFA
空間復雜度 查找表數 O(1) O(ms)
空間復雜度 存儲器使用量 \(O(\sum ^{ms})\) O(ms)
時間復雜度 處理周期 O(1) O(1)

對於FPGA來說更適合NFA實現,因為DFA方法的存儲器使用量呈指數型增加,而NFA方法的空間復雜度恆定

近似匹配

近似字符串匹配:在文本中查找和模式相似的字符串的問題

近似匹配過程中往往伴隨着對模式進行刪除、置換、插入等處理

一個典型的近似匹配硬件加速架構如下:

  • 宿主PC將文本和模式發送到硬件部分
  • 文本被發送到緩沖存儲器,模式被發送到編輯距離運算電路
  • 編輯距離運算電路從緩沖存儲器讀取一部分文本,並計算文本和模式的編輯距離。當且僅當編輯距離最小時,控制電路會將最小編輯距離和表示文本位置的地址輸出到FIFO保存
  • 文本每次移動一個字符並重復上一步
  • 所有文本匹配完成時,會將結果發回宿主PC

所有步驟中最復雜的就是計算編輯距離,在這里常常需要實現特殊的動態規划硬件算法

參考文獻

[1]Jason Cong,Yuzheng Ding. FlowMap: an optimal technology mapping algorithm for delay optimization in lookup-table based FPGA designs.[J]. IEEE Trans. on CAD of Integrated Circuits and Systems,1994,13(1).

[2]VPack-Jonathan Rose.UToronto

[3]Alexander (Sandy) Marquardt,Vaughn Betz,Jonathan Rose. Using cluster-based logic blocks and timing-driven packing to improve FPGA speed and density[P]. Field programmable gate arrays,1999.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM