🏠

SSR8 Reordering and Cyclic Shift HLS Kernels

概述

本模块包含三个关键的 PL (Programmable Logic) 数据重排序内核,它们共同实现了多相信道化器中复杂的样本路由和置换逻辑。这些内核运行在 312.5 MHz,负责在 DMA 端点和 AI Engine 阵列之间进行数据格式转换、缓冲管理和时序对齐。

可以把这三个内核想象成一个精密的物流分拣系统

  • permute_fb_i入库分拣员,将外部送来的包裹(样本)按特定规则重新排列后送入仓库(AIE Filterbank)
  • permute_fb_o出库整理员,将仓库出来的包裹恢复原始顺序
  • cyclic_shift最终配送员,根据目的地(频率通道)进行最后一轮排序调整

m16_ssr8_permute_fb_i:输入置换核

核心职责

该内核执行两个关键功能:

  1. Serpentine Shift(蛇形移位):实现 Circular Buffer 的 M×K 数组更新逻辑
  2. Card Dealing Permutation(发牌式置换):将逻辑通道动态映射到物理 AIE Tile

状态管理详解

// 来自 m16_ssr8_permute_fb_i.cpp
static ap_uint<3> fsm_state = 0;  // 8-state FSM for permutation pattern
static TT_DATA prev_data[2] = {0,0};  // 2-sample history for overlap handling

为什么需要 8 个状态?

过采样比 P/Q = 8/7 意味着每 8 个输出周期,输入样本的"相位"会完成一个完整循环。每个状态对应一种特定的置换模式,由 permute[NSTATE][M] 查找表定义:

static ap_uint<4> permute[NSTATE][M] = { 
  { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15},  // State 0: identity
  { 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, 0, 1},  // State 1: shift by 2
  // ... 每个状态对应不同的循环移位模式
};

这种设计源于数学原理:当 S = M×Q/P = 14 个新样本进入 M=16 的缓冲区时,有 2 个样本与历史重叠。8 状态 FSM 跟踪这种周期性模式。

延迟对齐机制

void align_latency( TT_DATA (&data_i_0)[M], TT_DATA (&data_i_1)[M],
                    TT_DATA (&data_o_0)[M], TT_DATA (&data_o_1)[M] )
{
  // AIE filterbank kernel is aligned to 8-cycle period (due to tap assignments).
  // So we introduce 6 cycles additional latency to compliment the 2 cycles 
  // added during stream formating
  static TT_DATA buff0[M][LATENCY] = { TT_DATA(0) };  // LATENCY = 3
  // ...
}

关键洞察:Filterbank 的调度周期是 8 周期(处理 8 个样本),而流格式化引入了 2 周期延迟。为了对齐到 8 周期边界,需要额外增加 6 周期延迟(通过 3 级双缓冲实现)。这保证了多个并行内核之间的同步。

流打包策略

// 偶数周期:准备 top_streams 和 bot_streams
// 奇数周期:输出 bot_streams(上一周期准备的)
// 这种 ping-pong 机制实现了无气泡流水线
if ( odd_cycle == 0 ) {
  FORMAT: for (int ss=0; ss < SSR_O; ss++) {
    top_streams[ss] = ( data_o_1[2*ss], data_o_0[2*ss], 
                        data_p_1[2*ss], data_p_0[2*ss] );
    bot_streams[ss] = ( data_o_1[2*ss+1], data_o_0[2*ss+1], 
                        data_p_1[2*ss+1], data_p_0[2*ss+1] );
  }
}

每个 128-bit AXI4-Stream 事务携带 4 个 cint16 样本(32-bit × 4),分布在两个时间片(top/bottom)上。这种打包方式与 AIE 的 64-bit PLIO 宽度匹配,Vitis 自动处理跨时钟域转换。


m16_ssr8_permute_fb_o:输出置换核

核心职责

逆向执行 permute_fb_i 的置换操作,将 Filterbank 输出的"物理 Tile 顺序"恢复为"逻辑通道顺序"。

解包与乒乓缓冲

// 使用 ping-pong 缓冲处理跨周期的数据对齐
static bool pong = 0;
static TT_DATA data0[2][M] = { TT_DATA(0) };  // [ping/pong][sample]
static TT_DATA data1[2][M] = { TT_DATA(0) };
// ... data2, data3

这里使用了两级乒乓

  1. odd_cycle 乒乓:处理输入流的偶/奇周期交错
  2. pong 乒乓:处理输出与输入的时间偏移

这种设计允许内核以 II=1 持续运行,同时处理数据依赖关系。

延迟对齐差异

注意到 permute_fb_oLATENCY = 2,而 permute_fb_iLATENCY = 3。这是因为:

  • permute_fb_i 的流格式化引入 2 周期延迟
  • permute_fb_o 的流解包引入 4 周期延迟
  • 两者都需要对齐到 8 周期 Filterbank 周期
  • 因此分别需要 6 和 4 的额外延迟补偿

m16_ssr8_cyclic_shift:循环移位核

核心职责

对 IDFT 输入应用周期性循环移位,补偿频率相关的相位偏移。这是信道化器算法的关键步骤,确保各通道正确下变频到基带。

数学原理

对于过采样比 P/Q = 8/7,循环移位的模式由以下公式决定:

\[\text{shift}_n = (n \times Q) \mod P\]

其中 \(n\) 是输出块索引。对于 M=16, P=8, Q=7:

  • 每个块移位量为 \((n \times 14) \mod 16\),但由于 SSR=8,实际表现为 8 状态 FSM
  • 每次处理两个向量(共 32 个样本),FSM 递增两次

实现细节

void cyclic_shift( TT_DATA (&data_i)[M], TT_DATA (&data_o)[M], 
                   ap_uint<3> fsm_state )
{
  CYCLIC_SHIFT: for (unsigned mm=0; mm < M; mm++) {
    data_o[(mm+2*fsm_state)&0xF] = data_i[mm];
  }
}

注意 2*fsm_state 的乘法:因为每次处理两个连续的 16 样本向量,FSM 状态的增量需要翻倍以反映正确的移位量。&0xF 实现对 M=16 的模运算。

流打包优化

// Writing four samples instead of two samples for 250 MHz
val_o(31,0)   = data_p_0[2*ss+0];
val_o(63,32)  = data_p_0[2*ss+1];
val_o(95,64)  = data_p_1[2*ss+0];
val_o(127,96) = data_p_1[2*ss+1];

注释提到 "250 MHz",但实际系统运行在 312.5 MHz。这可能是早期设计的遗留,或者指代其他配置下的目标频率。关键是每个 128-bit 事务现在携带 4 个样本(而不是 2 个),提高了总线效率。


HLS 优化技术总结

1. 流水线与启动间隔 (II)

所有三个内核都实现了 II=1(Initiation Interval = 1):

#pragma HLS pipeline II=1

这意味着每个时钟周期都能开始处理一个新的输入样本集。实现 II=1 的关键:

  • 完全展开内部循环(#pragma HLS INLINE 子函数)
  • 数组分区消除存储器端口竞争
  • 静态变量的 reset 指令保证可综合的初始化

2. 数组分区策略

#pragma HLS array_partition variable=data_i_0  // dim=0: 完全分区
#pragma HLS array_partition variable=buff0 dim=0  // 多维数组完全分区

完全分区(dim=0 或默认)将数组映射为独立的寄存器,支持并行访问。代价是资源消耗随维度指数增长——但对于 M=16 的小数组是可接受的。

3. 接口选择

#pragma HLS interface mode=ap_ctrl_none port=return  // 自由运行,无控制握手
#pragma HLS interface axis port=sig_i                 // AXI4-Stream 数据

ap_ctrl_none 是关键选择:这些内核是数据驱动的,不需要启动/完成握手。它们一旦复位就开始运行,从输入流读取,向输出流写入。这消除了控制开销,但也意味着:

  • 没有显式的启动/停止机制
  • 流量控制完全依赖 AXI Stream 的 back-pressure
  • 仿真时需要确保输入数据持续供应,否则会挂起

调试与验证建议

常见陷阱

  1. FSM 状态漂移:如果输入样本丢失或插入,fsm_state 会与预期错位,导致错误的置换模式。症状:输出频谱出现混叠或通道错位。

  2. 延迟不匹配:修改任何内核的流水线深度后,必须重新计算并调整 align_latency 中的延迟值。否则会导致 AIE 数据错位。

  3. 数组分区遗漏:忘记对新添加的数组应用 array_partition 会导致 II 增加,破坏实时性。

验证方法

每个 HLS 核目录都包含:

  • tb_wrapper.cpp:SystemC 测试平台
  • sig_i.txt / sig_o.txt:MATLAB 生成的黄金参考数据
  • gen_vectors.m:MATLAB 脚本生成测试向量

验证流程:

  1. 在 MATLAB 中运行 gen_vectors.m 生成参考数据
  2. 在 Vitis HLS 中运行 C Simulation
  3. 对比输出与 sig_o.txt
  4. 检查控制台输出的样本计数和校验和

与其他模块的关系

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  dma_stream_src │────→│ permute_fb_i     │────→│ AIE Filterbank  │
│  (7 streams)    │     │ (serpentine +    │     │ (8 tiles)       │
│                 │     │  card dealing)   │     │                 │
└─────────────────┘     └──────────────────┘     └─────────────────┘
                                                          │
┌─────────────────┐     ┌──────────────────┐              │
│  dma_stream_snk │←────│ permute_fb_o     │←─────────────┘
│  (8 streams)    │     │ (inverse perm)   │
│                 │     └──────────────────┘
└─────────────────┘               │
                                  ↓
                          ┌──────────────────┐
                          │ cyclic_shift     │
                          │ (phase rotation) │
                          └──────────────────┘
                                  │
                                  ↓
                          ┌──────────────────┐
                          │ AIE IDFT         │
                          │ (16 tiles)       │
                          └──────────────────┘

这种分层架构体现了关注点分离的设计原则:

  • DMA 端点处理内存接口突发传输
  • 置换核处理算法所需的数据重排序
  • AIE 专注于纯计算密集型任务

每个层级都有明确的契约(数据格式、时序、吞吐量),使得独立开发和验证成为可能。

On this page