🏠

PL Kernels 子模块详解

概述

本模块包含两个 HLS(高层次综合)实现的 PL(Programmable Logic)内核:random_noises2mm。它们分别作为数据流的起点和终点,构成了 AIE-PL 协同系统的 I/O 边界。

random_noise —— 可控噪声生成器

功能定位

想象这个内核是一个数字信号发生器,类似于实验室里的函数发生器。它能够产生可重复的伪随机复数信号,为 FIR 滤波器提供测试输入。

代码深度解析

static unsigned long int next = 1;

int my_rand(void) {
    next = next * 1103515245 + 12345;
    return (unsigned int)(next/65536) % 32768;
}

extern "C" void random_noise(hls::stream<std::complex<short> > & out, int size) {
#pragma HLS INTERFACE axis port=out
#pragma HLS INTERFACE s_axilite port=return bundle=control
#pragma HLS INTERFACE ap_ctrl_hs port=return
#pragma HLS interface s_axilite port=size bundle=control
#pragma HLS interface ap_none port=size
    for(int i=0;i<size;i++){
        std::complex<short> sample;
        sample.real((my_rand() % 256) - 127);
        sample.imag((my_rand() % 256) - 127);
        out.write(sample);
    }
}

接口设计分析

接口类型 用途 设计理由
axis (out) 流数据输出 与 AIE 的 AXI-Stream 接口直接兼容
s_axilite (size) 标量配置 允许主机动态设置输出样本数
ap_ctrl_hs 启动/完成握手 标准的 HLS 内核控制协议

随机数生成器的工程考量

为什么选择自定义 LCG 而不是标准库 rand()

  1. 确定性:固定种子 next = 1 确保每次仿真产生相同的序列,便于调试和结果比对
  2. 可移植性:不依赖宿主机的 C 库实现,跨平台行为一致
  3. 资源效率:LCG 只需一次乘法和加法,硬件实现成本低

注意:这是一个教学用的简化实现。生产环境中可能需要:

  • 外部种子注入机制(通过额外的 s_axilite 寄存器)
  • 更复杂的 PRNG 算法(如 Tausworthe 或 xorshift)
  • 输出范围的可配置性

内存与性能特征

  • 无内部存储:纯流式处理,无需 BRAM
  • II = ?:虽然未显式标注 PIPELINE,但简单的循环体通常能达到 II=1
  • 延迟\(O(size)\),每个样本需要 2 次随机数生成和 1 次流写入

s2mm —— Stream to Memory Mapper

功能定位

这是数据流的终点站。它把 AIE 输出的连续流数据转换为 DDR 内存中的离散存储块,供主机后续读取和验证。

代码深度解析

void s2mm(ap_int<32>* mem, hls::stream<ap_axis<32, 0, 0, 0>>& s, int size) {
#pragma HLS INTERFACE m_axi port=mem offset=slave bundle=gmem
#pragma HLS interface axis port=s
#pragma HLS INTERFACE s_axilite port=mem bundle=control
#pragma HLS INTERFACE s_axilite port=size bundle=control
#pragma HLS interface s_axilite port=return bundle=control
    for(int i = 0; i < size; i++) {
#pragma HLS PIPELINE II=1
        ap_axis<32, 0, 0, 0> x = s.read();
        mem[i] = x.data;
    }
}

接口设计分析

接口类型 信号 说明
m_axi mem DDR 内存访问,支持突发传输
axis s AXI-Stream 输入,来自 AIE
s_axilite mem, size 标量配置:目标地址和数据量

关键设计决策

为什么使用 ap_axis<32, 0, 0, 0> 而不是简单 hls::stream<int>

ap_axis 模板参数的含义:

  • 32:TDATA 宽度 32 位
  • 0:TUSER 宽度 0(无用户定义边带信号)
  • 0:TDEST 宽度 0(无路由信息)
  • 0:TID 宽度 0(无事务 ID)

这种显式声明确保了与 AIE 接口的精确匹配,避免了协议不匹配导致的静默错误。

流水线性能保证

#pragma HLS PIPELINE II=1

这行 pragma 是性能的关键承诺:

  • II=1:每个时钟周期处理一个样本
  • 隐含假设:DDR 突发传输带宽足够支撑这一速率
  • 潜在瓶颈:如果 DDR 延迟过高,实际 II 可能大于 1

内存访问模式

mem[i] = x.data;

这是一个顺序写模式,对 DDR 非常友好:

  • 连续的地址空间允许高效的突发传输
  • 无读-修改-写操作,避免缓存一致性开销
  • 适合使用写合并缓冲区优化

资源利用预估

资源类型 估计用量 原因
BRAM 0 无内部缓冲
DSP 0 无算术运算
FF/LUT 简单的状态机和 AXI 协议逻辑
外部带宽 \(size \times 4\) bytes 32-bit 每样本

配置文件解析

random_noise.cfg

[hls]
flow_target=vitis
syn.file=random_noise.cpp
syn.cflags=-I.
syn.top=random_noise
syn.debug.enable=1
package.ip.name=random_noise
package.output.syn = true
package.output.format=xo
package.output.file=random_noise.xo

关键配置项

  • syn.debug.enable=1:启用调试信息,便于波形分析时符号解析
  • package.output.format=xo:输出 Vitis 内核对象格式,用于后续链接

s2mm.cfg

结构与 random_noise.cfg 类似,仅 syn.toppackage.ip.name 不同。


集成注意事项

1. 数据宽度匹配

  • random_noise 输出:std::complex<short> = 16-bit I + 16-bit Q = 32-bit 总线
  • s2mm 输入:ap_axis<32, ...> = 32-bit 总线
  • 关键点:两者必须严格匹配,否则会出现数据错位或协议错误

2. 时钟域考虑

system.cfg 中可以看到 PL 内核运行在独立的时钟域:

# 隐含的时钟分配由 Vitis 链接器自动处理
# PL 内核通常运行在 100-300 MHz
# AIE 阵列运行在 1 GHz+

AXI-Stream 接口的 TVALID/TREADY 握手机制天然支持跨时钟域传输,无需额外同步逻辑。

3. 复位行为

HLS 生成的内核遵循标准 AXI 复位协议:

  • ARESETN 低电平有效
  • 复位期间所有输出处于安全状态
  • 复位后需要重新配置 s_axilite 寄存器

调试技巧

波形分析要点

在 XSIM 中观察这些信号:

random_noise

  • out_TVALID/out_TREADY:握手是否顺畅?
  • out_TDATA:数据值是否符合预期(-127 到 128 范围)?
  • ap_start/ap_done:内核是否正确响应启动信号?

s2mm

  • s_TVALID/s_TREADY:输入侧是否有反压(stall)?
  • m_axi_gmem_AWADDR/AWLEN:突发传输是否正确发起?
  • m_axi_gmem_WVALID/WREADY:写数据通道是否畅通?

常见问题排查

现象 可能原因 排查方法
无数据输出 内核未启动 检查 ap_start 信号
数据不连续 AIE 反压 观察 TREADY 拉低时段
写地址错误 基址配置错误 检查 s_axilite 写入值
数据错位 位宽不匹配 对比 TDATAmem 内容

扩展建议

如果需要修改这些内核以适应其他应用场景:

  1. 改变数据类型:修改 std::complex<short>intfloat,需同步更新 AIE 内核接口
  2. 添加数据校验:在 s2mm 中增加 CRC 或校验和计算
  3. 支持非连续地址:将 mem[i] 改为 mem[offset + i*stride],实现分散-聚集 DMA
  4. 多通道支持:实例化多个 random_noises2mm,通过 TDEST 区分通道
On this page