3. 数据突发传输(Burst)机制:Burst传输原理、HLS中Burst配置、Burst长度对性能的影响

各位同学,咱们今天聊聊DDR读写里最核心的一个机制——突发传输,也就是Burst。说白了,Burst就是一次申请,连续读写多个数据。为什么要有这个机制?你想想看,每次读写DDR都要经历行激活、列选、预充电这一套流程,光开销就几十个时钟周期。如果只读一个数,那效率太低了。

我个人习惯把Burst比作“拼车”。单次读写就像一个人打车,起步价全掏;Burst就是一群人拼一辆车,平摊下来每个人成本低得多。DDR内部也是这样,一次Burst能连续读写2、4、8甚至16个数据,平摊掉地址命令的开销。

3.1 Burst传输原理

DDR的Burst传输,核心在于“连续地址”。你只要给出起始地址和Burst长度,DDR内部会自动递增地址,连续读写数据。我遇到过不少新手,以为Burst就是一次发多个地址,其实不是——你只发一次地址,剩下的地址由DDR自己算。

这里有个关键点:Burst传输必须地址连续。如果地址不连续,比如读地址0x1000,下一个读0x2000,那就没法Burst,只能拆成两次单次读写。所以,数据在内存中的布局直接影响Burst效率。

Burst传输的典型时序:

  • 发送读命令 + 起始地址
  • 等待CAS延迟(CL)
  • 连续读出Burst Length个数据
  • 每个时钟周期输出一个数据

嗯,这里要注意:DDR的Burst长度通常是2的幂次,比如2、4、8、16。为什么?因为DDR内部的数据预取架构决定了这个。DDR3/DDR4的Burst长度固定为8,DDR5可以支持16甚至32。你想想看,Burst长度越长,单次读写的效率越高,但延迟也越大——因为你要等最后一个数据出来才能用。

3.2 HLS中Burst配置

在HLS里配置Burst,说白了就是告诉工具:我这个接口要支持突发传输。Vivado HLS里最常用的就是AXI4 Master接口,它天然支持Burst。

我给大家看一个典型的配置代码:

// HLS中配置AXI4 Master Burst接口
#include <hls_stream.h>
#include <ap_axi_sdata.h>

void ddr_burst_example(
    hls::stream<ap_axis<32,1,1,1> > &in_stream,
    hls::stream<ap_axis<32,1,1,1> > &out_stream,
    volatile ap_uint<32> *ddr_mem  // AXI4 Master接口
) {
    #pragma HLS INTERFACE m_axi port=ddr_mem depth=1024 offset=slave
    #pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> data_in;
    ap_axis<32,1,1,1> data_out;

    // 从输入流读取数据,写入DDR
    for (int i = 0; i < 256; i++) {
        #pragma HLS PIPELINE II=1
        in_stream.read(data_in);
        ddr_mem[i] = data_in.data;
    }

    // 从DDR读取数据,输出到流
    for (int i = 0; i < 256; i++) {
        #pragma HLS PIPELINE II=1
        data_out.data = ddr_mem[i];
        data_out.last = (i == 255) ? 1 : 0;
        out_stream.write(data_out);
    }
}

这段代码里,m_axi接口默认就会使用Burst传输。HLS会自动分析循环的访问模式,如果地址连续,它就会生成Burst请求。我曾经遇到过一个坑:循环里如果用了ddr_mem[i * 2]这种非连续地址,HLS就退化成单次读写,性能直接掉到十分之一。

HLS Burst配置小技巧:

  • 使用#pragma HLS INTERFACE m_axi时,可以加max_read_burst_length和max_write_burst_length参数,限制最大Burst长度
  • 默认Burst长度是16,但DDR控制器可能不支持这么长,建议根据实际硬件调整
  • 如果数据量小(比如几十个),Burst反而可能降低效率——因为Burst的建立开销比单次读写还大

3.3 Burst长度对性能的影响

Burst长度对性能的影响,说白了就是“效率 vs 延迟”的权衡。我给大家画个图,直观感受一下:

Burst长度 vs 读写效率 1 2 4 8 16 Burst长度 0% 50% 100% 效率 效率趋于饱和 单次读写 Burst=8 延迟(相对值) 效率 延迟

从这张图可以看得很清楚:Burst长度从1增加到8时,效率提升非常明显;但超过8之后,效率增长就变缓了。为什么?因为DDR内部有行缓冲(Row Buffer),Burst长度超过行缓冲大小后,就需要重新激活行,反而增加开销。

我给大家一个经验数据:

Burst长度 效率(相对单次读写) 适用场景
1 1x 随机小数据访问
2 1.8x 少量数据,地址不连续
4 3.2x 中等数据量,地址连续
8 5.5x 大数据量,地址连续(推荐)
16 6.8x 超大数据量,DDR5支持

避坑指南:

我曾经在一个项目中,把Burst长度设成了64,以为越大越好。结果仿真发现性能反而下降了。后来查手册才知道,DDR控制器的最大Burst长度是16,超过16会被自动截断,而且每次截断都要重新仲裁,效率反而更低。所以,Burst长度不是越大越好,要匹配硬件支持的最大值。

另外,Burst长度还影响总线利用率。你想想看,如果Burst长度是8,那么一次Burst占用总线8个周期;如果Burst长度是1,一次只占1个周期,但地址命令开销占了大部分。所以,对于多端口访问的场景,Burst长度太长会导致其他端口等待时间变长,反而降低整体吞吐量。

我个人习惯的做法是:

  • 对于单端口、大数据量的场景,用最大Burst长度(通常是16)
  • 对于多端口、混合访问的场景,用Burst长度4或8,平衡效率和公平性
  • 对于随机小数据访问,干脆不用Burst,用单次读写反而更灵活

HLS中调整Burst长度的实用方法:

在Vivado HLS中,可以通过config_interface命令调整Burst长度:

config_interface -m_axi_max_read_burst_length 8
config_interface -m_axi_max_write_burst_length 8

这样生成的IP核,最大Burst长度就被限制在8了。我建议在综合前先做一次C仿真,看看实际的Burst行为是否符合预期。

好了,关于Burst传输的原理、配置和性能影响,就讲到这里。记住一句话:Burst是DDR性能的倍增器,但用不好也可能成为瓶颈。下一节我们聊聊如何通过数据重排来优化Burst效率,那才是真正体现工程师水平的地方。

可可精品资料库