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:输入置换核
核心职责
该内核执行两个关键功能:
- Serpentine Shift(蛇形移位):实现 Circular Buffer 的 M×K 数组更新逻辑
- 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
这里使用了两级乒乓:
- odd_cycle 乒乓:处理输入流的偶/奇周期交错
- pong 乒乓:处理输出与输入的时间偏移
这种设计允许内核以 II=1 持续运行,同时处理数据依赖关系。
延迟对齐差异
注意到 permute_fb_o 的 LATENCY = 2,而 permute_fb_i 是 LATENCY = 3。这是因为:
permute_fb_i的流格式化引入 2 周期延迟permute_fb_o的流解包引入 4 周期延迟- 两者都需要对齐到 8 周期 Filterbank 周期
- 因此分别需要 6 和 4 的额外延迟补偿
m16_ssr8_cyclic_shift:循环移位核
核心职责
对 IDFT 输入应用周期性循环移位,补偿频率相关的相位偏移。这是信道化器算法的关键步骤,确保各通道正确下变频到基带。
数学原理
对于过采样比 P/Q = 8/7,循环移位的模式由以下公式决定:
其中 \(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
- 仿真时需要确保输入数据持续供应,否则会挂起
调试与验证建议
常见陷阱
-
FSM 状态漂移:如果输入样本丢失或插入,
fsm_state会与预期错位,导致错误的置换模式。症状:输出频谱出现混叠或通道错位。 -
延迟不匹配:修改任何内核的流水线深度后,必须重新计算并调整
align_latency中的延迟值。否则会导致 AIE 数据错位。 -
数组分区遗漏:忘记对新添加的数组应用
array_partition会导致 II 增加,破坏实时性。
验证方法
每个 HLS 核目录都包含:
tb_wrapper.cpp:SystemC 测试平台sig_i.txt/sig_o.txt:MATLAB 生成的黄金参考数据gen_vectors.m:MATLAB 脚本生成测试向量
验证流程:
- 在 MATLAB 中运行
gen_vectors.m生成参考数据 - 在 Vitis HLS 中运行 C Simulation
- 对比输出与
sig_o.txt - 检查控制台输出的样本计数和校验和
与其他模块的关系
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 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 专注于纯计算密集型任务
每个层级都有明确的契约(数据格式、时序、吞吐量),使得独立开发和验证成为可能。