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,我们需要对这个阵列进行充分分区。在本例中,该阵列实现为一组基于触发器的寄存器。
拆分输入流的过程耳熟能详,把来自一个流的数据字正确地路由到一个流阵列即可。
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,我们需要对这个阵列进行充分分区。在本例中,该阵列实现为一组基于触发器的寄存器。
拆分输入流的过程耳熟能详,把来自一个流的数据字正确地路由到一个流阵列即可。
- 基于FPGA的片上系统的无线保密通信终端(02-16)
- 基于FPGA的DVI/HDMI接口实现(05-13)
- 基于PLB总线的H.264整数变换量化软核的设计(03-20)
- FPGA 重复配置和测试的实现(08-14)
- 经I/O优化的FPGA(04-23)
- 基于Actel FPGA的PWM IP的应用(09-17)