微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > FPGA和CPLD > Vivado HLS推动协议处理系统蓬勃发展(下)

Vivado HLS推动协议处理系统蓬勃发展(下)

时间:10-30 来源:互联网 点击:
  接上篇
  4 设置简单系统
  协议处理一般情况下属于状态事务。必须先顺序读取在多个时钟周期内进入总线的数据包字,然后根据数据包的某些字段决定进一步操作。通常应对这种处理的方法是使用状态机,对数据包进行迭代运算,完成必要的处理。例3是一种简单的状态机,用于根据上一级的输入丢弃或转发数据包。该函数接收三个参数:一个是通过“inData”流接收到的输入分组数据;一个是通过“validBuffer”流显示数据包是否有效的1位旗标;第三个是称为“outData”的输出分组数据流。注意Vivado HLS函数中的参数是按引用传递的。这在使用较为复杂的Vivado HLS流的时候是必要的。ap_uint等较为简单的数据类型则可按值传递。
  第2行中的流水线编译指令指示Vivado HLS将该函数流水线化,让初始化间隔为1(II=1),即每个时钟周期处理一个新的输入数据字。Vivado HLS负责核验设计,并确定需要在设计中引入多少个流水线级来满足调度限制要求。
  例3:使用Vivado HLS的有限状态机
  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行用于声明一个静态枚举变量,用于表达该FSM中的状态。使用枚举与否可以选择,不过能让代码更容易阅读,因为可以给状态适当地命名。不过使用任何整数或ap_unit变量也能得到与之类似的结果。第5行用于声明一个“axiWord”类型的变量,用于存储准备从输入中读取的分组数据。
  第7行中的开关语句用于表达实际的状态机。建议使用开关,但非强制要求。使用if-else决策树也能执行同样的功能。开关语句能够让Vivado HLS工具更高效地枚举所有状态,并优化得到的状态机RTL代码。
  执行从D_IDLE状态开始,此时FSM从第10行和第11行的两个输入流读取。这两行分别代表两种流对象读取方法。这两种方法均从设定的流读取,然后将结果存储到给定变量中。这种方法采取阻塞式读取,意味着如果该方法调用无法顺序执行,就会暂停执行该函数调用中的其余代码。在试图读取空流的时候会发生这种情况。
                               
                  5 流分割和合并
  在协议处理中,根据协议栈特定字段转发数据包给不同模块,然后在发送前将不同的流重新组合,是一项关键功能。Vivado HLS允许使用高级架构来推动这一转发过程,具体如例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 }
  本例体现的是模块合并功能的使用,其中一个流阵列作为输入(inData),一个单流作为输出(outData)。这个模块的功能是以无区别的方式从输入流读取数据,然后将读取的数据输出给输出流。该模块采用双级FSM实现,其结构与前文介绍的结构一致。
  FSM的第一个状态用于确保选择输入流的无区别性(fairness)。实现的方法是使用循环算法检查队列。该算法在完成上一队列的访问之后,即从下一队列起查找新的数据。第17到19行的代码采用的即是此循环算法。常量NUM_MERGE_STREAMS用于设定待合并的流的数量。接下来的第20行负责测试当前的流,其内容用tempCntr变量标示。如果当前流非空,则将其设置为活跃流(第21行)。然后从该流中读取数据(第22行)。如果读取的数据字不是最后一个数据字(由第24行负责检查),则状态机进入M_STREAM状态,然后输出来自该流的剩余数据字。在处理完成最后一个数据字后,FSM返回M_IDLE状态,然后重复上述过程。
  这个模块引入了一个新的编译指令,称为“array_partition”。该编译指令能让Vivado HLS了解为了提高吞吐量,是否需要把一个阵列拆分为多个子阵列。如果未加设定,Vivado HLS会使用双端口BRAM来访问阵列。如果要在一个时钟周期中访问阵列两次以上,如果不适当地提高初始化间隔(II)的值,该工具将无法调度这些访问。在本例中,略去array_partition编译指令,将NUN_MERGE_STREAMS值设为8,就可以让II=4。但因为想能够在每个时钟周期内访问steamEmpty阵列的所有元素,让目标II=1,我们需要对这个阵列进行充分分区。在本例中,该阵列实现为一组基于触发器的寄存器。
  拆分输入流的过程耳熟能详,把来自一个流的数据字正确地路由到一个流阵列即可。
  

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top