写在前面
本文使用UG871的示例工程,第一个工程说明什么是块级 I/O 协议以及如何控制它们;第二个工程进行设置端口IO协议;第三个工程对数组接口进行设计;第四个工程使用AXI接口设计。
块级接口协议
新建工程并添加文件
添加UG871提供的文件如下:
将adders.c和adders.h添加到source。
adders.c
#include "adders.h" int adders(int in1, int in2, int in3) { // Prevent IO protocols on all input ports #pragma HLS INTERFACE ap_none port=in3 #pragma HLS INTERFACE ap_none port=in2 #pragma HLS INTERFACE ap_none port=in1 int sum; sum = in1 + in2 + in3; return sum; }
adders.h
#ifndef ADDERS_H_ #define ADDERS_H_ int adders(int in1, int in2, int in3); #endif
完成添加后进行c综合。
此时生成的报告中的接口列表如下:
此时可以看到,在接口列表中,出现了start、done、idle、ready、return等信号,这些信号也可以称为块级IO的控制信号,用于对数据传输的控制。块级 I/O 协议允许 RTL 设计由独立于数据 I/O 端口的附加端口控制。 此 I/O 协议与功能本身相关联,而不与任何数据端口相关联。 默认的块级 I/O 协议称为 ap_ctrl_hs。
下表分析了ap_ctrl_hs中各个信号的功能。
从波形中也可以看出每个信号的功能
修改块级接口
在directive中,点击函数名,然后进行添加指令。
- ap_ctrl_none:无块级 I/O 控制协议。
- ap_ctrl_hs:块级I/O 控制握手协议。
- ap_ctrl_chain:用于控制链接的块级I/O协议。这个I/O协议主要用于将流水线块链接在一起。
- s_axilite: 可以应用在ap_ctrl_hs或ap_ctrl_chain之外,作为AXI Slave Lite接口来实现块级I/O协议,以代替单独的离散I/O端口。
如果选择无块级 I/O 控制协议,将不会出现相应的IO控制接口信号。如下图:
端口IO协议
新建工程并添加文件
新建工程并添加文件,添加UG871提供的文件如下: 将adders_io.c和adders_io.h添加到source。
adders_io.c
#include "adders_io.h" void adders_io(int in1, int in2, int *in_out1) { *in_out1 = in1 + in2 + *in_out1; }
adders_io.h
#ifndef ADDERS_IO_H_ #define ADDERS_IO_H_ void adders_io(int in1, int in2, int *in_out1); #endif
完成添加后进行c综合。
此时默认生成的端口列表如下:
修改端口IO接口
在directive中,点击需要修改端口的变量,然后进行添加指令。
ap_none:指定端口不添加I/0协议。这个端口的参数被实现为一个没有其他相关信号的数据端口。ap none模式是标量输入的默认模式。
ap_stable模式:用于只在设备处于重置模式时才会改变的配置输入。
ap_ovld与in-out参数一起使用。当将in-out分为单独的输入端口和输出端口时,ap_none应用于输入端口,ap_vld应用于输出端口。
ap_ovld:这是指针实参的默认值,可读可写。
如果把in1修改成ap_vld,in2修改成ap_ack。综合生成的信号列表将会出现一个in1的有效信号,和一个in2的响应信号。如下图:
数组接口设计
新建工程并添加文件
添加UG871提供的文件如下: 将array_io.c和array_io.h添加到source。
array_io.c
#include "array_io.h" void array_io (dout_t d_o[N], din_t d_i[N]) { int i, rem; // Store accumulated data static dacc_t acc[CHANNELS]; dacc_t temp; // Accumulate each channel For_Loop: for (i=0;i<N;i++) { rem=i%CHANNELS; temp = acc[rem] + d_i[i]; acc[rem] = temp; d_o[i] = acc[rem]; } }
array_io.h
#ifndef ARRAY_IO_H_ #define ARRAY_IO_H_ #include <stdio.h> typedef short din_t; typedef short dout_t; typedef int dacc_t; #define CHANNELS 8 #define SAMPLES 4 #define N CHANNELS * SAMPLES void array_io (dout_t d_o[N], din_t d_i[N]); #endif
完成添加后进行c综合。
此时默认生成的端口列表如下:
默认情况下,只要数组在顶级函数上,就会使用ap_memory。无论数组的类型是什么(input, output, in/out), ap_memory都是默认值。
修改数组接口设计
将数组参数合成到 RAM 端口是默认设置。 可以使用许多其他选项来控制这些端口的实现方式。 如使用单端口或双端口 RAM 接口;使用FIFO接口;划分为离散端口。
HLS将 RAM 接口指定为单端口或双端口。 如果不进行选择,Vivado HLS 会自动分析设计并选择端口数量以最大化数据速率。
在这个设计中,如果想使用多个 RTL 端口实现一个数组参数,必须进行展开 for 循环并允许所有内部操作并行发生,否则多个端口没有任何好处。for 循环确保一次只能读取(或写入)一个数据样本。
所以为了将数组接口转换为双端口,首先要进行数组的循环展开优化。
在directive界面选择loop的标签进行添加循环展开优化。
尝试将端口设置为双端口RAM和FIFO,假设设置d_i为双端口RAM接口,d_o为FIFO接口。同样的方法进行插入相关指令。
数组参数d_o 已实现为具有16 位数据端口(d_o_din) 和相关输出写入(d_o_write) 和输入FIFO 满(d_o_full_n) 端口的FIFO 接口。 参数d_i 已实现为双端口RAM 接口。
性能报告:
通过使用双端口 RAM 接口,该设计可以以两倍于先前设计的速率接受输入数据。 由于 for 循环已展开,因此循环中的逻辑能够以该速率消耗数据。 默认情况下,每个循环迭代都会依次执行。 此实现代码将逻辑限制为在每次迭代中对 d_i 进行一次读取。 展开循环允许执行更多读取(但会创建 N 个逻辑副本)。 但是,在输出上使用单端口 FIFO 接口,输出数据速率与以前相同 。