一、HLS中的AXI接口:声明方式与综合属性

大家好,我是你们的FPGA设计讲师。今天我们来聊聊HLS中AXI接口的那些事。说实话,我刚接触HLS时,也被这些接口类型搞得有点晕——hls::stream、ap_fifo、m_axi,到底什么时候该用哪个?

我个人习惯把AXI接口理解成「数据搬运的通道」。不同的通道,搬运方式不一样,适用场景也不同。咱们一个一个来看。

1.1 hls::stream——流式接口的王者

先说说hls::stream。这个接口类型,说白了就是FIFO的抽象。数据像流水一样,一个接一个地进来,再一个接一个地出去。

声明方式很简单:

// 定义一个输入流
hls::stream<ap_uint<32>> input_stream("input_stream");
// 定义一个输出流
hls::stream<ap_uint<32>> output_stream("output_stream");

嗯,这里要注意:hls::stream必须指定一个名字字符串。这个名字会作为RTL中FIFO的实例名。我在项目中遇到过,名字起得太随意,调试时找信号找得想哭。

hls::stream的特点:

  • 数据按顺序读写,不支持随机访问
  • 读写操作必须成对出现(写一次,读一次)
  • 综合后映射为FIFO或简单的握手信号
  • 默认深度为1,但可以配置

核心要点:hls::stream最适合做数据流处理,比如视频像素流、网络包处理。我做过一个视频缩放模块,输入输出全是hls::stream,代码写起来特别清爽。

1.2 ap_fifo——更底层的FIFO接口

ap_fifo和hls::stream有点像,但更底层。它直接对应RTL中的FIFO端口。

// ap_fifo声明
ap_fifo<ap_uint<64>, 16> fifo_in;  // 64位宽,深度16

ap_fifo vs hls::stream:

特性 ap_fifo hls::stream
抽象层次 低(接近RTL) 高(C++风格)
读写接口 read()/write() << / >> 操作符
空满判断 支持empty()/full() 支持empty()/full()
综合效率 较高 较高

我个人建议:除非你需要精细控制FIFO深度和状态,否则优先用hls::stream。代码可读性更好,调试也方便。

1.3 m_axi——内存映射接口

m_axi就是AXI4主接口。它和前面两个完全不同——它支持随机访问,可以读写任意地址的数据。

// m_axi声明
void my_kernel(
    ap_uint<64> *m_axi_data,  // 自动推断为m_axi
    int size
) {
    #pragma HLS INTERFACE m_axi port=m_axi_data depth=1024
    // ...
}

m_axi的关键属性:

  • depth:指定最大访问深度,影响BRAM资源
  • offset:地址偏移模式(slave、off等)
  • latency:读延迟,默认64,可调
  • bundle:多个端口共享一个AXI接口

避坑指南:我曾经在m_axi的depth属性上栽过跟头。如果depth设得太小,综合工具会认为你只需要那么点存储空间,结果实际运行时地址越界,数据全乱了。建议depth设得比实际需求大一些,留点余量。

1.4 接口综合属性详解

接口综合属性,就是告诉HLS工具「这个接口该怎么实现」。常用的有这些:

属性 适用接口 说明
INTERFACE 所有 指定接口协议(ap_ctrl_none、ap_ctrl_hs等)
DEPTH m_axi FIFO或存储深度
LATENCY m_axi 读数据延迟周期数
BUNDLE m_axi 多个端口共享物理接口
OFFSET m_axi 地址偏移模式

举个例子,一个完整的接口声明长这样:

#pragma HLS INTERFACE m_axi port=data depth=4096 latency=32 bundle=gmem0
#pragma HLS INTERFACE s_axilite port=size bundle=control
#pragma HLS INTERFACE ap_ctrl_none port=return

你想想看,这些属性组合起来,能玩出很多花样。比如把多个m_axi端口绑定到同一个bundle,可以节省AXI接口资源,但要注意地址不能冲突。

1.5 三种接口的选择策略

说了这么多,到底怎么选?我总结了一个简单的判断流程:

  1. 数据是流式处理吗? → 用hls::stream
  2. 需要精确控制FIFO状态? → 用ap_fifo
  3. 需要随机访问外部存储? → 用m_axi
  4. 既要流式又要随机访问? → 混合使用,但注意资源开销

我的经验:刚开始学HLS时,总想把所有接口都用m_axi,觉得它最灵活。后来发现,能用hls::stream的地方尽量用,综合出来的电路更小、时序更好。m_axi适合做大块数据的搬运,比如从DDR读一帧图像。

1.6 知识体系总览

为了让大家更直观地理解这三种接口的关系,我画了一张图:

HLS AXI接口知识体系 hls::stream 流式接口(FIFO抽象) ap_fifo 底层FIFO接口 m_axi 内存映射接口 共同点:都支持数据流传输,综合后映射为硬件FIFO或AXI接口 适用场景 视频流、网络包 数据流水线 适用场景 需要精确控制FIFO 低延迟场景 适用场景 DDR/SRAM访问 随机地址读写 选择策略:流式处理用hls::stream,精细控制用ap_fifo,随机访问用m_axi

这张图把三种接口的关系和适用场景都串起来了。你可以把它当作一个快速参考,做设计时对照着选型。

1.7 实战小贴士

最后分享几个我在项目中积累的经验:

  • 接口命名要规范:hls::stream的名字会出现在RTL中,建议用有意义的名称,比如"video_in"、"packet_out"
  • 深度设置要合理:hls::stream默认深度为1,如果上下游速率不匹配,记得用DEPTH属性增加深度
  • m_axi的latency别乱调:默认64通常够用,调小了可能时序不过,调大了浪费资源
  • 多用ap_ctrl_none:如果模块不需要等待控制信号,用ap_ctrl_none可以省掉一堆握手逻辑

记住:接口选型没有的对错,关键看你的数据流特征。多试几次,你就能找到最适合自己设计的组合。

好了,这一章的内容就到这里。三种接口的声明方式和综合属性,说白了就是HLS和硬件之间的「翻译规则」。理解透了,写出来的代码才能高效、可靠。

可可精品资料库