接上篇 4 設置簡單系統(tǒng) 協(xié)議處理一般情況下屬于狀態(tài)事務。必須先順序讀取在多個時鐘周期內進入總線的數(shù)據(jù)包字,然后根據(jù)數(shù)據(jù)包的某些字段決定進一步操作。通常應對這種處理的方法是使用狀態(tài)機,對數(shù)據(jù)包進行迭代運算,完成必要的處理。例3是一種簡單的狀態(tài)機,用于根據(jù)上一級的輸入丟棄或轉發(fā)數(shù)據(jù)包。該函數(shù)接收三個參數(shù):一個是通過“inData”流接收到的輸入分組數(shù)據(jù);一個是通過“validBuffer”流顯示數(shù)據(jù)包是否有效的1位旗標;第三個是稱為“outData”的輸出分組數(shù)據(jù)流。注意Vivado HLS函數(shù)中的參數(shù)是按引用傳遞的。這在使用較為復雜的Vivado HLS流的時候是必要的。ap_uint等較為簡單的數(shù)據(jù)類型則可按值傳遞。 第2行中的流水線編譯指令指示Vivado HLS將該函數(shù)流水線化,讓初始化間隔為1(II=1),即每個時鐘周期處理一個新的輸入數(shù)據(jù)字。Vivado HLS負責核驗設計,并確定需要在設計中引入多少個流水線級來滿足調度限制要求。 例3:使用Vivado HLS的有限狀態(tài)機 1 void dropper(stream& inData, stream>& validBuffer, stream& outData) { 2 #pragma HLS pipeline II=1 enable_flush 3 4 static enum dState {D_IDLE = 0, D_STREAM, D_ DROP} dropState; 5 axiWord currWord = {0, 0, 0, 0}; 6 7 switch(dropState) { 8 case D_IDLE: 9 if (!validBuffer.empty() && !inData.empty()) { 10 ap_uint valid = validBuffer.read(); 11 inData.read(currWord); 12 if (valid) { 13 outData.write(currWord); 14 dropState = D_STREAM; 15 } 16 } 17 else 18 dropState = D_DROP; 19 break; 20 case D_STREAM: 21 if (!inData.empty()) { 22 inData.read(currWord); 23 outData.write(currWord); 24 if (currWord.last) 25 dropState = D_IDLE; 26 } 27 break; 28 case D_DROP: 29 if (!inData.empty()) { 30 inData.read(currWord); 31 if (currWord.last) 32 dropState = D_IDLE; 33 } 34 break; 35 } 36 } 第4行用于聲明一個靜態(tài)枚舉變量,用于表達該FSM中的狀態(tài)。使用枚舉與否可以選擇,不過能讓代碼更容易閱讀,因為可以給狀態(tài)適當?shù)孛2贿^使用任何整數(shù)或ap_unit變量也能得到與之類似的結果。第5行用于聲明一個“axiWord”類型的變量,用于存儲準備從輸入中讀取的分組數(shù)據(jù)。 第7行中的開關語句用于表達實際的狀態(tài)機。建議使用開關,但非強制要求。使用if-else決策樹也能執(zhí)行同樣的功能。開關語句能夠讓Vivado HLS工具更高效地枚舉所有狀態(tài),并優(yōu)化得到的狀態(tài)機RTL代碼。 執(zhí)行從D_IDLE狀態(tài)開始,此時FSM從第10行和第11行的兩個輸入流讀取。這兩行分別代表兩種流對象讀取方法。這兩種方法均從設定的流讀取,然后將結果存儲到給定變量中。這種方法采取阻塞式讀取,意味著如果該方法調用無法順序執(zhí)行,就會暫停執(zhí)行該函數(shù)調用中的其余代碼。在試圖讀取空流的時候會發(fā)生這種情況。 5 流分割和合并 在協(xié)議處理中,根據(jù)協(xié)議棧特定字段轉發(fā)數(shù)據(jù)包給不同模塊,然后在發(fā)送前將不同的流重新組合,是一項關鍵功能。Vivado HLS允許使用高級架構來推動這一轉發(fā)過程,具體如例4中所示的流合并。 例4:簡單的流合并情況 1 void merge(stream inData[NUM_MERGE_ STREAMS], stream&outData) { 2 #pragma HLS INLINE off 3 #pragma HLS pipeline II=1 enable_flush 4 5 static enum mState{M_IDLE = 0, M_STREAM} mergeState; 6 static ap_uint rrCtr = 0; 7 static ap_uint streamSource = 0; 8 axiWord inputWord = {0, 0, 0, 0}; 9 10 switch(mergeState) { 11 case M_IDLE: 12 bool streamEmpty[NUM_MERGE_STREAMS]; 13 #pragma HLS ARRAY_PARTITION variable=stream- Empty complete 14 for (uint8_t i=0;i 15 streamEmpty = inData.empty(); 16 for (uint8_t i=0;i 17 uint8_t tempCtr = streamSource + 1 + i; 18 if (tempCtr >= NUM_MERGE_STREAMS) 19 tempCtr -= NUM_MERGE_STREAMS; 20 if(!streamEmpty[tempCtr]) { 21 streamSource = tempCtr; 22 inputWord = inData[streamSource]. read(); 23 outData.write(inputWord); 24 if (inputWord.last == 0) 25 mergeState = M_STREAM; 26 break; 27 } 28 } 29 break; 30 case M_STREAM: 31 if (!inData[streamSource].empty()) { 32 inData[streamSource].read(inputWord); 33 outData.write(inputWord); 34 if (inputWord.last == 1) 35 mergeState = M_IDLE; 36 } 37 break; 38 } 39 } 本例體現(xiàn)的是模塊合并功能的使用,其中一個流陣列作為輸入(inData),一個單流作為輸出(outData)。這個模塊的功能是以無區(qū)別的方式從輸入流讀取數(shù)據(jù),然后將讀取的數(shù)據(jù)輸出給輸出流。該模塊采用雙級FSM實現(xiàn),其結構與前文介紹的結構一致。 FSM的第一個狀態(tài)用于確保選擇輸入流的無區(qū)別性(fairness)。實現(xiàn)的方法是使用循環(huán)算法檢查隊列。該算法在完成上一隊列的訪問之后,即從下一隊列起查找新的數(shù)據(jù)。第17到19行的代碼采用的即是此循環(huán)算法。常量NUM_MERGE_STREAMS用于設定待合并的流的數(shù)量。接下來的第20行負責測試當前的流,其內容用tempCntr變量標示。如果當前流非空,則將其設置為活躍流(第21行)。然后從該流中讀取數(shù)據(jù)(第22行)。如果讀取的數(shù)據(jù)字不是最后一個數(shù)據(jù)字(由第24行負責檢查),則狀態(tài)機進入M_STREAM狀態(tài),然后輸出來自該流的剩余數(shù)據(jù)字。在處理完成最后一個數(shù)據(jù)字后,F(xiàn)SM返回M_IDLE狀態(tài),然后重復上述過程。 這個模塊引入了一個新的編譯指令,稱為“array_partition”。該編譯指令能讓Vivado HLS了解為了提高吞吐量,是否需要把一個陣列拆分為多個子陣列。如果未加設定,Vivado HLS會使用雙端口BRAM來訪問陣列。如果要在一個時鐘周期中訪問陣列兩次以上,如果不適當?shù)靥岣叱跏蓟g隔(II)的值,該工具將無法調度這些訪問。在本例中,略去array_partition編譯指令,將NUN_MERGE_STREAMS值設為8,就可以讓II=4。但因為想能夠在每個時鐘周期內訪問steamEmpty陣列的所有元素,讓目標II=1,我們需要對這個陣列進行充分分區(qū)。在本例中,該陣列實現(xiàn)為一組基于觸發(fā)器的寄存器。 拆分輸入流的過程耳熟能詳,把來自一個流的數(shù)據(jù)字正確地路由到一個流陣列即可。 6 抽取字段和重新對齊字段 在包處理中,抽取字段和重新對齊字段是最基本的操作之一。由于數(shù)據(jù)包一般是經過多個時鐘周期內通過總線到達模塊的,常見的情況是需要的字段要么在它們抵達的數(shù)據(jù)字中未能對齊,要么分散在多個數(shù)據(jù)字中(往往兩種情況都有)。因此要處理這些字段,必須將它們從數(shù)據(jù)流中抽取出來,存入緩存然后重新對齊以便處理。 例5:源MAC地址抽取示例 1 if (!inData.empty()) { 2 inData.read(currWord); 3 switch(wordCount) { 4 case 0: 5 MAC_DST = currWord.data.range(47, 0); 6 MAC_SRC.range(15, 0) = currWord.data. range(63, 48); 7 break; 8 case 1: 9 MAC_SRC.range(47 ,16) = currWord. data.range(31, 0); 10 break; 11 case 2: 12 …… 例5是一個非常簡單的字段抽取和再對齊示例。這個示例從以太網報頭中抽取源MAC地址。數(shù)據(jù)通過稱為“inData”的64位流抵達。在每個時鐘周期讀入數(shù)據(jù)(第2行)。隨后根據(jù)讀取的數(shù)據(jù)字執(zhí)行合適的語句。因此在第5行中源MAC地址的頭16位被抽取出來,并移位到MAC_SRC變量的起始部分。在下一時鐘周期中,MAC地址的其余32位抵達總線,然后存入MAC_SRC變量的32位更高位中。 7 用多級層級創(chuàng)建系統(tǒng) 上文討論了如何使用Vivado HLS實現(xiàn)簡單的三級流水線。但是一般的包處理系統(tǒng)可能會包含分布在層級結構中多個層面的多個模塊。圖2即是這種系統(tǒng)的示例。在本例中,層級結構的第一層由兩個模塊組成,每個模塊下面包括三個子模塊。這個示例中的頂層模塊與前面介紹的簡單系統(tǒng)中頂層模塊相似。但包含有三個子模塊的較低層模塊使用INLINE編譯指令來解析函數(shù),將其子模塊推送到頂層,如例6所示。 例6:Vivado HLS中的中間模塊 1 void module2 (stream &inData, stream&outData) { 2 #pragma HLS INLINE 3 4 ……… 因此在Vivado HLS完成綜合后,系統(tǒng)基本如圖3所示。這樣Vivado HLS就能正確地根據(jù)這些模塊創(chuàng)建數(shù)據(jù)流架構,完成模塊的流水線化,然后同步執(zhí)行。在嵌入該函數(shù)后,各模塊和信號保持原來的名稱不變。 8 使用高級語言結構 高層次綜合的主要優(yōu)勢之一在于可以使用高級語言結構來表達復雜對象,與傳統(tǒng)RTL設計相比,顯著提高了抽象水平。下面的例子是描述一個小型查找表。 例7中的代碼用于內容可尋址存儲器(CAM)類定義,它使用類對象創(chuàng)建一個表,供存儲和恢復上述原型系統(tǒng)的ARP數(shù)據(jù)。該類有一個私有成員,這個私有成員是一個由“noOfArpTableEntries”條“arpTableEntry”類型記錄組成的陣列。這種類型屬于一種數(shù)據(jù)結構,包括MAC地址、對應的IP地址和用于說明該條記錄是否包含有效數(shù)據(jù)的一個數(shù)位。 例7:CAM類定義 1 class cam { 2 private: 3 arpTableEntry filterEntries[noOfArpTableEntries]; 4 public: 5 cam(); 6 bool write(arpTableEntry writeEntry); 7 bool clear(ap_uint clearAddress); 8 arpTableEntry compare(ap_uint searchAddress); 9 }; 這個類也包括四種在這個表上運算方法(其中一個是構造器)。其中的一個,即比較法,用于實現(xiàn)真正的查找功能。本例通過提供IP地址來返回相應的MAC地址。處理的方法是使用“for”循環(huán)查找表中的每一條記錄,搜索有相同IP地址的有效記錄。然后完整地返回這條記錄。如果沒有找到,就返回無效記錄。為讓設計實現(xiàn)II=1的目標,必須完全展開這個循環(huán)。 例8:用于CAM類的比較法 1 arpTableEntry cam::compare(ap_uint searchAddress) { 2 for (uint8_t i=0; i 3 if (this->filterEntries.valid == 1 && searchAddress == this->filterEntries.ipAddress) 4 return this->filterEntries; 5 } 6 arpTableEntry temp = {0, 0, 0}; 7 return temp; 8 } 上述經驗和示例明確說明,用戶可以使用Vivado HLS充分發(fā)揮高級編程結構的作用,用類似軟件的方法描述包處理系統(tǒng)。采用RTL是難以實現(xiàn)的。 9 10GBps速率下的協(xié)議處理 與傳統(tǒng)RTL相比,Vivado HLS可使用C/C++在FPGA上迅速方便地實現(xiàn)協(xié)議處理設計,充分發(fā)揮高級語言帶來的效率提升優(yōu)勢。另外還具有下列優(yōu)點:使用C函數(shù)輕松完成系統(tǒng)構建;數(shù)據(jù)通過流交換,提供類似FIFO的標準化接口;靈活的流控制和HLS編譯指令,便于使用該工具實現(xiàn)需要的架構。借助這些功能,用戶無需重寫源代碼就能夠迅速判研多種不同設計方案的利弊。 出于解釋這類設計的基本概念的目的,上文討論了一種能夠應答ping和ARP請求,解析IP地址查詢的簡單ARP服務器。結果證明用Vivado HLS設計的模塊能夠以10Gbp乃至更高的線速完成協(xié)議處理。 |