當設計者試圖從算法中獲得最佳性能但軟件方法已無計可施時,可以嘗試通過硬件/軟件重新劃分來進行加速。FPGA易于實現軟件模塊和硬件模塊的相互交換,且不必改變處理器或進行板級變動。本文闡述如何用FPGA來實現算法的硬件加速。 如果想從代碼中獲得最佳性能,方法包括優化算法、使用查找表而不是算法、將一切都轉換為本地字長尺寸、使用注冊變量、解開循環甚至可能采用匯編代碼。如果所有這些都不奏效,可以轉向更快的處理器、采用一個不同的處理器架構,或將代碼一分為二通過兩個處理器并行處理。不過,如果有一種方法可將那些對時間有嚴格要求的代碼段轉換為能夠以5-100倍速度運行的函數調用,而且如果這一方法是一種可供軟件開發之用的標準工具,這可信嗎?現在,利用可編程邏輯作為硬件加速的基礎可使這一切都變成現實。 圖1:帶定制指令的可配置處理器架構。 低成本可編程邏輯在嵌入式系統中應用得越來越普遍,這為系統設計者提供了一個無需對處理器或架構進行大的改動即可獲得更高性能的可選方案。可編程邏輯可將計算密集型功能轉換為硬件加速功能。從軟件的角度看,這只是簡單地將一個函數調用做進一個定制的硬件模塊中,但運行速度要比通過匯編語言優化的相同代碼或將算法轉換為查找表要快得多。 1 硬件加速 首先探討一下什么是硬件加速,以及將算法作為定制指令來實現與采用硬件外圍電路的區別。硬件加速是指利用硬件模塊來替代軟件算法以充分利用硬件所固有的快速特性。從軟件的角度看,與硬件加速模塊接口就跟調用一個函數一樣。唯一的區別在于此函數駐留在硬件中,對調用函數是透明的。 取決于算法的不同,執行時間最高可加快100倍。硬件在執行各種操作時要快得多,如執行復雜的數學功能、將數據從一個地方轉移到另一個地方,以及多次執行同樣的操縱。本文后面將討論一些通常用軟件完成的操作,經過硬件加速后這些操作可獲得極大的性能提高。 如果在系統設計中采用FPGA,那么在設計周期的任何時候都可以添加定制的硬件。設計者可以立刻編寫軟件代碼,并可在最終定稿之前在硬件部分上運行。此外,還可以采取增量法來決定哪部分代碼用硬件而不是軟件來實現。FPGA供應商所提供的開發工具可實現硬件和軟件之間的無縫切換。這些工具可以為總線邏輯和中斷邏輯生成HDL代碼,并可根據系統配置定制軟件庫及include文件。 2 帶一些CISC的RISC 精簡指令集計算(RISC)架構的目標之一即是保持指令簡單化,以便讓指令運行得足夠快。這與復雜指令集計算(CISC)架構正好相反,后者一般不會同樣快地執行指令,但每個指令可完成更多處理任務。這兩種架構應用得都很普遍,而且各有所長。 如果能根據特定的應用將RISC的簡單和快速特性與CISC強大的處理能力結合起來,豈不兩全其美?其實這正是硬件加速所要做的。加入為某種應用而定制的硬件加速模塊可以提高處理能力,并減少代碼復雜性和密度,因為硬件模塊取代了軟件模塊。可以這么說,是用硬件來換取速度和簡單性。 定制指令和硬件外圍電路方式有兩種硬件加速模塊實現方式。其一是定制指令,它幾乎可在每一個可配置處理器中實現,這是采用可配置處理器的主要優點。如圖1所示,定制指令是作為算術邏輯單元(ALU)的擴展而添加的。處理器只知道定制指令就像其它指令一樣,包括擁有自己的操作代碼。至于C代碼,宏可自動生成,從而使得使用該定制指令跟調用函數一樣。 如果定制指令需要幾個時鐘周期才能完成,而且要連續調用它,則可以流水線式定制指令來實現。這樣可在每個時鐘周期產生一個結果,不過開始時有些延遲。 硬件加速模塊的另一種實現方式是硬件外圍電路。在這一方式下,數據不是傳遞給軟件函數,而是寫入存儲器映射的硬件外圍電路中。計算是在CPU之外完成的,因此在外圍電路工作的同時CPU可以繼續運行代碼。其實代替軟件算法的只是一個普通的硬件外圍電路。與定制指令的另一個不同之處是硬件外圍電路可以訪問系統中的其它外圍電路或存儲器,而無須CPU介入。 根據硬件需要做什么、怎么工作以及需要多長時間可以決定采用是定制指令還是硬件外圍電路更合適。對于那些在幾個周期內就可完成的操作,定制指令一般更好些,因為它產生的開銷要更少。對于外圍電路,一般需要執行幾個指令來寫入控制寄存器、狀態寄存器和數據寄存器,而且需要一個指令來讀取結果。如果計算需要幾個周期,實施外圍電路比較好,因為它不會影響CPU流水線。或者,也可以實施前面所述的流水線式定制指令。 另一個區別是定制指令需要有限數目的操作數,并返回一個結果。根據處理器指令集架構的不同,操作數也各異。對某些操縱,這樣可能顯得很麻煩。此外,如果需要硬件從存儲器或存儲器中的其它外圍電路讀出和寫入,則必須采用硬件外圍電路,因為定制指令無法訪問總線。 圖2:16位CRC算法的硬件實現。(Optional) 3 選擇代碼 當需要優化C語言代碼以滿足某些速度要求時,可能要運行一個代碼仿制工具,或親自檢查該代碼以便了解代碼的哪個部分導致系統停滯。當然,這需要熟悉代碼以便知道瓶頸在哪兒。 即便找出瓶頸所在,如何優化也是個挑戰。有些方案采用本地字大小的變量、帶預先計算值的查找表,以及通用軟件算法優化。這些技巧可產生快幾倍的執行速度效果。另一種優化C算法的方法是用匯編語言編寫。過去這種方法可獲得很好的提高,但現今的編譯器在優化C算法上已做得很好,因此這種性能的提高是有限的。如果需要顯著的性能提高,傳統的軟件算法優化技巧恐怕是不夠的。 然而,利用硬件實施的算法比軟件實施要強100倍,這不足為奇。那么,如何確定將哪些代碼轉為硬件實施呢?大可不必將整個軟件模塊轉換為硬件,而應選擇那些在硬件中運行得特別快的操作,比如將數據從一處復制到另一處、大量的數學運算以及任何運行多次的循環。如果一個任務由幾個數學運算組成,還可以考慮在硬件中加速整個任務。有些時候,僅加速任務中的一個操作就可滿足性能要求。 4 實例:CRC算法的硬件加速 由于大量且重復的計算,循環冗余校驗(CRC)算法或任何“校驗和”算法都是硬件加速的不錯選擇。下面通過一個CRC算法的優化過程來探討如何實現硬件加速。 首先,利用傳統的軟件技巧來優化算法,然后將其轉向定制指令以加速算法。我們將討論不同實現方法的性能比較和折衷。 CRC算法可用來校驗數據在傳輸過程中是否被破壞。這些算法很流行,因為它們具有很高的檢錯率,而且不會對數據吞吐量造成太大影響,因為CRC校驗位被添加進數據信息中。但是,CRC算法比一些簡單的校驗和算法有更大的計算量要求。盡管如此,檢錯率的提高使得這種算法值得去實施。 一般說來,發送端對要被發送的消息執行CRC算法,并將CRC結果添加進該消息中。消息的接收端對包括CRC結果在內的消息執行同樣的CRC操作。如果接收端的結果與發送端的不同,這說明數據被破壞了。 CRC算法是一種密集的數學運算,涉及到二元模數除法(modulo-2 division),即數據消息被16或32位多項式(取決于所用CRC標準)除所得的余數。這種操作一般通過異或和移位的迭代過程來實現,當采用16位多項式時,這相當于每數據字節要執行數百條指令。如果發送數百個字節,計算量就會高達數萬條指令。因此,任何優化都會大幅提高吞吐量。 代碼列表1中的CRC函數有兩個自變量(消息指針和消息中的字節數),它可返回所計算的CRC值(余數)。盡管該函數的自變量是一些字節,但 算要逐位來執行。該算法并不高效,因為所有操作(與、移位、異或和循環控制)都必須逐位地執行。 列表1:逐位執行的CRC算法C代碼。 /* * The width of the CRC calculation and result. * Modify the typedef for a 16or32-bit CRC standard. */ typedef unsigned char crc; #define WIDTH (8 * sizeof(crc)) #define TOPBIT (1 <<(WIDTH - 1)) crc crcSlow(unsigned char const message[], int nBytes) { crc remainder = 0; /* * Perform modulo-2 division, a byte at a time. */ for (int byte = 0; byte { /* * Bring the next byte into the remainder. */ remainder ^= (message[byte] <<(WIDTH - 8)); /* * Perform modulo-2 division, a bit at a time. */ for (unsigned char bit = 8; bit > 0; "bit) { /* * Try to divide the current data bit. */ if (remainder &TOPBIT) { remainder = (remainder <<1) ^ POLYNOMIAL; } else { remainder = (remainder <<1); } } } /* * The final remainder is the CRC result. */ return (remainder); } 1).傳統的軟件優化 圖3:帶CRC外圍電路和DMA的系統模塊示意圖。 讓我們看一下如何利用傳統的軟件技巧來優化CRC算法。因為CRC操作中的一個操作數,即多項式(除數)是常數,字節寬CRC操作的所有可能結果都可以預先計算并存儲在一個查找表中。這樣,通過一個讀查找表動作就可讓操作按逐個字節執行下去。 采用這一算法時,需要將這些預先計算好的值存儲在存儲器中。選擇ROM或RAM都可以,只要在啟動CRC計算之前將存儲器初始化就行。查找表有256個字節,表中每個字節位置包含一個CRC結果,共有256種可能的8位消息(與多項式大小無關)。 列表2示出了采用查找表方法的C代碼,包括生成查找表crcInit()中數值的代碼。 crc crcTable[256]; void crcInit(void) { crc remainder; /* * Compute the remainder of each possible dividend. */ for (int dividend = 0; dividend <256; ++dividend) { /* * Start with the dividend followed by zeros. */ remainder = dividend <<(WIDTH - 8); /* * Perform modulo-2 division, a bit at a time. */ for (unsigned char bit = 8; bit > 0; "bit) { /* * Try to divide the current data bit. */ if (remainder &TOPBIT) { remainder = (remainder <<1) ^ POLYNOMIAL; } else { remainder = (remainder <<1); } } /* * Store the result into the table. */ crcTable[dividend] = remainder; } } /* crcInit() */ crc crcFast(unsigned char const message[], int nBytes) { unsigned char data; crc remainder = 0; /* * Divide the message by the polynomial, a byte at a time. */ for (int byte = 0; byte { data = message[byte] ^ (remainder >> (WIDTH - 8)); remainder = crcTable[data] ^ (remainder <<8); } /* * The final remainder is the CRC. */ return (remainder); } /* crcFast() */ 整個計算減少為一個循環,每字節(不是每位)有兩個異或、兩個移位操作和兩個裝載指令。基本上,這里是用查找表的存儲空間來換取速度。該方法比逐位計算的方法要快9.9倍,這一提高對某些應用已經足夠。如果需要更高的性能,可以嘗試編寫匯編代碼或增加查找表容量以擠出更多性能來。但是,如果需要20、50甚至500倍的性能提高,就要考慮采用硬件加速來實現該算法了。 表1:各種規模的數據模塊下CRC算法測試比較結果。 2).采用定制指令方法 CRC算法由連續的異或和移位操作構成,用很少的邏輯即可在硬件中簡單實現。由于這一硬件模塊僅需幾個周期來計算CRC,采用定制指令來實現CRC計算要比采用外圍電路更好。此外,無須涉及系統中任何其它外圍電路或存儲器。僅需要一個微處理器來支持定制指令即可,一般是指可配置微處理器。 當在硬件中實現時,算法應該每次執行16或32位計算,這取決于所采用的CRC標準。如果采用CRC-CCITT標準(16位多項式),最好每次執行16位計算。如果使用8位微處理器,效率可能不太高,因為裝載操作數值及返回CRC值需要額外的周期。圖2示出了用硬件實現16位CRC算法的內核。 信號msg(15..0)每次被移入異或/移位硬件一位。列表3示出了在64KB數據模塊上計算CRC的一些C代碼例子。該實例是針對Nios嵌入式處理器。 列表3:采用定制指令的CRC計算C代碼。 unsigned short crcCompute(unsigned short *data_block, unsigned int nWords) { unsigned short* pointer; unsigned short word; /* * initialize crc reg to 0xFFFF */ word = nm_crc (0xFFFF, 1); /* nm_crc() is the CRC custom instruction */ /* * calculate CRC on block of data * nm_crc() is the CRC custom instruction * */ for (pointer = data_block; pointer <(data_block + nWords); pointer ++) word = nm_crc(*pointer, 0) return (word); } int main(void) { #define data_block_begin (na_onchip_memory) #define data_block_end (na_onchip_memory + 0xffff) unsigned short crc_result; unsigned int data_block_length = (unsigned short *)data_block_end - (unsigned short *)data_block_begin + 1; crc_result = crcCompute((unsigned short *)data_block_begin, data_block_length); } 采用定制指令時,用于計算CRC值的代碼是一個函數調用,或宏。當針對Nios處理器實現定制指令時,系統構建工具會生成一個宏。在本例中為nm_crc(),可用它來調用定制指令。 在啟動CRC計算之前,定制指令內的CRC寄存器需要先初始化。裝載初始值是CRC標準的一部分,而且每種CRC標準都不一樣。接著,循環將為數據模塊中的每16位數據調用一次CRC定制指令。這種定制指令實現方式要比逐位實現的方法快27倍。 3).CRC外圍電路方法 如果將CRC算法作為硬件外圍電路來實現,并利用DMA將數據從存儲器轉移到外圍電路,這樣還可以進一步提高速度。這種方法將省去處理器為每次計算而裝載數據所需要的額外周期。DMA可在此外圍電路完成前一次CRC計算的時鐘周期內提供新的數據。圖3示出了利用DMA、CRC外圍電路來實現加速的系統模塊示意圖。 在64KB數據模塊上,利用帶DMA的定制外圍電路可獲得比逐位計算的純軟件算法快500倍的性能。要知道,隨著數據模塊規模的增加,使用DMA所獲得的性能也隨之提高。這是因為設置DMA僅需很少的開銷,設置之后DMA運行得特別快,因為每個周期它都可以傳遞數據。因此,若只有少數字節的數據,用DMA并不劃算。 這里所討論的所有采用CRC-CCITT標準(16位多項式)的算法都是在Altera Stratix FPGA的Nios處理器上實現的。表1示出了各種數據長度的測試比較結果,以及大致的硬件使用情況(FPGA中的存儲器或邏輯單元)。 可以看出,算法所用的硬件越多,算法速度越快。這是用硬件資源來換取速度。 5 FPGA的優點 當采用基于FPGA的嵌入式系統時,在設計周期之初不必為每個模塊做出用硬件還是軟件的選擇。如果在設計中間階段需要一些額外的性能,則可以利用FPGA中現有的硬件資源來加速軟件代碼中的瓶頸部分。由于FPGA中的邏輯單元是可編程的,可針對特定的應用而定制硬件。因此,僅使用所需要的硬件即可,而不必做出任何板級變動(前提是FPGA中的邏輯單元足夠用)。設計者不必轉換到另一個新的處理器或者編寫匯編代碼,就可做到這一點。 使用帶可配置處理器的FPGA可獲得設計靈活性。設計者可以選擇如何實現軟件代碼中的每個模塊,如用定制指令,或硬件外圍電路。此外,還可以通過添加定制的硬件而獲取比現成微處理器更好的性能。 另一點要知道的是,FPGA有充裕的資源,可配置處理器系統可以充分利用這一資源。 算法可以用軟件,也可用硬件實現。出于簡便和成本考慮,一般利用軟件來實現大部分操作,除非需要更高的速度以滿足性能指標。軟件可以優化,但有時是不夠的。如果需要更高的速度,利用硬件來加速算法是一個不錯的選擇。 FPGA使軟件模塊和硬件模塊的相互交換更加簡便,不必改變處理器或進行板級變動。設計者可以在速度、硬件邏輯、存儲器、代碼大小和成本之間做出折衷。利用FPGA可以設計定制的嵌入式系統,以增加新的功能特性及優化性能。 |
Xilinx FPGA最新價格查詢查詢,datasheet資料下載 http://www.icbom.com |
FPGA 硬件加速平臺(卡)、高性能計算平臺 http://www.aoshi-tech.cn/wzxxy_zt?article_id=38 |
FPGA硬件加速平臺/加速卡、高性能計算平臺 |