🏠

Farrow Filter Design Variants 技术深度解析

一、模块概览:这个模块解决了什么问题?

1.1 问题空间:分数延迟滤波的需求

在数字信号处理中,Farrow滤波器是一种用于实现**分数延迟(fractional delay)**的多相滤波器结构。想象你有一个数字信号采样序列,但你需要获取"两个采样点之间某个位置"的信号值——这不是简单的整数延迟,而是任意分数位置的插值。

实际应用场景:

  • 采样率转换(Sample Rate Conversion)
  • 符号同步(Symbol Timing Recovery)
  • 波束形成中的时延补偿
  • 软件无线电中的数字下变频

1.2 硬件实现挑战

将Farrow滤波器映射到**AMD AIE-ML(AI Engine-ML)**架构面临独特挑战:

  1. 计算密度:Farrow结构涉及多个多项式系数与输入信号的乘累加
  2. 内存访问模式:需要高效利用AIE的存储器层次结构
  3. 数据流编排:输入延迟参数和信号数据的同步输入
  4. 吞吐量与资源权衡:不同优化策略在性能与资源占用间的取舍

1.3 模块设计意图

本模块是一个渐进式优化教程,展示如何将Farrow滤波器从初始移植版本逐步优化到最终高性能实现。四个设计变体构成一个完整的学习路径:

变体 阶段定位 核心优化目标
farrow_port_initial 基线移植 功能正确性验证,初始AIE映射
farrow_opt_1 第一阶段优化 内存访问模式优化,减少延迟
farrow_opt_2 第二阶段优化 计算并行度提升,吞吐量优化
farrow_final_aie 最终实现 综合优化,生产级性能

关键洞察:四个变体的dut_graph包装层几乎完全相同,真正的优化发生在内部的farrow_graph实现中。这种设计允许优化演进时保持测试接口的一致性。


二、心智模型:如何理解这个模块的抽象?

2.1 三层架构比喻

想象一个工厂流水线:

外部世界 (PL - Programmable Logic)
    ↕  [PLIO接口 - 工厂的装卸码头]
测试封装层 (dut_graph)
    ↕  [stream连接 - 内部传送带]
核心算法层 (farrow_graph)
    ↕  [kernel计算 - 加工车间]
AIE计算阵列

三层职责划分:

层级 组件 职责 变化频率
物理接口层 input_plio/output_plio 与外部PL世界的数据交换 极低
测试封装层 dut_graph 接口标准化、仿真/硬件条件编译
算法实现层 farrow_graph Farrow滤波器核心实现 高(优化迭代)

2.2 核心抽象概念

AIE Graph(数据流图)

dut_graph继承自adf::graph,这是AMD AIE开发框架的核心抽象。它描述的是静态数据流而非控制流:

  • 节点(Node):计算单元(kernel)或I/O接口(PLIO)
  • 边(Edge):数据流通道,这里是connect<stream>建立的流式连接
  • 执行语义:数据驱动,当输入数据可用时kernel自动触发

PLIO(Programmable Logic I/O)

PLIO是AIE与外部可编程逻辑(PL)之间的数据通道抽象

// 创建PLIO实例 - 如同在芯片上定义一个物理引脚
input_plio::create("PLIO_i_0", plio_64_bits, "data/sig_i.txt");
//                        ↑名称      ↑位宽           ↑仿真数据文件

关键特性

  • plio_64_bits指定数据总线宽度
  • 仿真模式下可以从文件读取输入数据,硬件运行时则从实际PL逻辑接收
  • 通过条件编译#ifdef AIE_SIM_ONLY实现仿真/硬件行为切换

2.3 数据流视角

想象信号数据像水流一样通过系统:

sig_i.txt (仿真) 或 PL逻辑 (硬件)
    ↓
PLIO_i_0 [输入阀门]
    ↓ stream连接 (管道)
farrow.sig_i [ farrow_graph的入口 ]
    ↓
[ Farrow滤波处理 - 内部kernel网络 ]
    ↓
farrow.sig_o [ farrow_graph的出口 ]
    ↓ stream连接 (管道)
PLIO_o_0 [输出阀门]
    ↓
sig_o.txt (仿真) 或 PL逻辑 (硬件)

关键理解dut_graph本身不处理数据,它只是编排数据流路径。真正的计算发生在farrow_graph内部的AIE kernel中。


三、架构与数据流:端到端的数据旅程

3.1 系统架构图

graph TB subgraph "PL 可编程逻辑层" PL_I0[PLIO_i_0
信号输入 64-bit] PL_I1[PLIO_i_1
延迟输入 64-bit] PL_O0[PLIO_o_0
信号输出 64-bit] end subgraph "AIE 图容器层 (dut_graph)" direction TB SIG_I[std::array<input_plio,1>
sig_i] DEL_I[std::array<input_plio,1>
del_i] SIG_O[std::array<output_plio,1>
sig_o] SIG_I -->|connect<stream>| FG_SIG[farrow.sig_i] DEL_I -->|connect<stream>| FG_DEL[farrow.del_i] FG_OUT[farrow.sig_o] -->|connect<stream>| SIG_O end subgraph "核心算法层 (farrow_graph)" direction LR K1[AIE Kernel
多项式系数计算] K2[AIE Kernel
分数延迟插值] K3[AIE Kernel
输出重构] K1 --> K2 --> K3 end PL_I0 -.-> SIG_I PL_I1 -.-> DEL_I SIG_O -.-> PL_O0 FG_SIG -.-> K1 FG_DEL -.-> K2 K3 -.-> FG_OUT

3.2 构造时数据流建立

dut_graph的构造函数在编译时静态建立整个数据通路。让我们追踪构造过程:

class dut_graph : public graph {
  farrow_graph farrow;  // 核心算法图实例
public:
  std::array<input_plio,1> sig_i;   // 信号输入端口数组
  std::array<input_plio,1> del_i;   // 延迟参数输入端口数组  
  std::array<output_plio,1> sig_o;  // 信号输出端口数组
  
  dut_graph(void) {
    // 阶段1: 创建PLIO接口实例
    #ifdef AIE_SIM_ONLY
      // 仿真模式: 从文本文件读取输入,输出写入文件
      sig_i[0] = input_plio::create("PLIO_i_0", plio_64_bits, "data/sig_i.txt");
      del_i[0] = input_plio::create("PLIO_i_1", plio_64_bits, "data/del_i_optimized.txt");
      sig_o[0] = output_plio::create("PLIO_o_0", plio_64_bits, "data/sig_o.txt");
    #else
      // 硬件模式: 与真实PL逻辑连接,无文件路径
      sig_i[0] = input_plio::create("PLIO_i_0", plio_64_bits);
      del_i[0] = input_plio::create("PLIO_i_1", plio_64_bits);
      sig_o[0] = output_plio::create("PLIO_o_0", plio_64_bits);
    #endif
    
    // 阶段2: 建立数据流连接
    connect<stream>(sig_i[0].out[0], farrow.sig_i[0]);  // 信号输入 → farrow图
    connect<stream>(del_i[0].out[0], farrow.del_i[0]);  // 延迟输入 → farrow图
    connect<stream>(farrow.sig_o[0], sig_o[0].in[0]);    // farrow图输出 → 外部
  }
};

关键观察

  1. 双模式编译:通过AIE_SIM_ONLY宏,同一套代码支持纯软件仿真(使用文本文件作为数据源)和真实硬件运行(与PL逻辑交互)
  2. 流式连接语义connect<stream>建立的是无缓冲流通道,数据一旦产生立即传输,适合高吞吐量低延迟的信号处理场景
  3. 端口数组设计:使用std::array封装单个PLIO,为未来多通道扩展预留接口(如从单通道扩展到8通道只需改模板参数)

3.3 运行时执行流程

// main函数展示典型的AIE图执行模式
int main(void) {
  aie_dut.init();    // 阶段1: 初始化 - 加载AIE配置,建立数据通道
  aie_dut.run(4);    // 阶段2: 运行 - 启动图执行,参数4表示迭代次数
  aie_dut.end();     // 阶段3: 结束 - 刷新缓冲区,关闭通道,释放资源
  return 0;
}

执行语义

  • run(4) 表示图会执行4次"迭代",具体含义取决于farrow_graph的实现
  • 对于流式处理,一次"迭代"可能对应处理一个数据块(如1024个采样点)
  • AIE图的执行是数据驱动的:PLIO输入有数据时,farrow_graph自动触发执行

四、关键设计决策与权衡分析

4.1 为什么采用"包装器"设计模式?

观察到的设计dut_graph只是farrow_graph的薄包装层,两者接口几乎一致。

决策理由

设计选择 优点 代价
分离dut_graph与farrow_graph 1. dut_graph负责测试基础设施(PLIO设置、条件编译)
2. farrow_graph专注算法实现,可在不同测试环境中复用
3. 优化演进时,测试代码无需改动
增加一层间接性,初学者可能困惑于"为什么需要两层图"
如果合并为一层 概念简单,少一个文件 算法代码与测试代码耦合,优化时每次都要改测试基础设施

结论:这是**关注点分离(Separation of Concerns)**的典型应用。dut_graph是"测试夹具(test fixture)",farrow_graph是"被测器件(DUT, Device Under Test)"。

4.2 条件编译:AIE_SIM_ONLY的意义

#ifdef AIE_SIM_ONLY
  // 仿真路径:连接文本文件
#else
  // 硬件路径:连接真实PL接口
#endif

设计权衡

维度 仿真模式 (AIE_SIM_ONLY) 硬件模式
输入来源 文本文件(确定性、可重复) 实时PL逻辑(真实信号)
输出目标 文本文件(便于离线分析) PL逻辑/内存(系统集成)
调试能力 高(可单步、可查看中间文件) 低(需ILA等硬件调试器)
执行速度 慢(纯软件模拟) 快(硬件加速)
验证阶段 单元测试、算法验证 系统集成、性能测试

关键洞察:同一套源代码支持两种模式,意味着算法开发者在仿真环境中验证功能正确性后,无需修改代码即可在硬件上运行。这实现了"一次编写,到处运行"的可移植性。

4.3 为什么使用std::array包装单个PLIO?

观察到的代码std::array<input_plio,1> sig_i; 然后只使用sig_i[0]

决策分析

方案对比

方案 代码示例 优缺点
A: 直接用单个对象 input_plio sig_i; 简单,但无法扩展到多通道
B: 用std::array包装 std::array<input_plio,1> sig_i; 当前用1个,但模板参数改为8即可支持8通道
C: 用std::vector std::vector<input_plio> sig_i; 动态大小,但AIE图要求编译期确定拓扑

结论:选择B是为了未来可扩展性。AIE图的拓扑结构必须在编译时确定,std::array提供编译期大小信息,同时保持扩展的灵活性。这是**面向未来的设计(future-proofing)**的典型实践。

4.4 为什么是connect<stream>而非其他连接类型?

ADF框架支持多种连接类型streamwindowcascade等。

选择stream的理由

连接类型 语义 适用场景 本模块选择
stream 无缓冲流式传输,数据即时传递 连续数据流、低延迟信号处理 选用
window 缓冲数据传输,块式处理 批量数据、需要完整数据块才能计算 不适用
cascade 级联链式传输 FIR滤波器级联等特定架构 不适用

Farrow滤波器的特性

  • 输入是连续采样的信号流,非块状数据
  • 要求低延迟,采样处理即时产生输出
  • 分数延迟计算需要即时访问当前和若干历史采样

结论stream连接类型完美匹配Farrow滤波器的流式信号处理特性,实现真正的数据驱动执行


五、代码深度分析:关键实现细节

5.1 类结构与继承关系

// 继承关系:dut_graph 是一个 adf::graph
class dut_graph : public graph {
  farrow_graph farrow;  // 组合关系:包含一个farrow_graph实例
  // ... 成员声明
};

面向对象设计分析

关系类型 实现方式 设计意图
继承 (is-a) dut_graph : public graph dut_graph是一种AIE图,获得ADF框架的所有能力(初始化、运行、连接管理等)
组合 (has-a) farrow_graph farrow; dut_graph拥有一个Farrow滤波器实现,通过组合复用其功能

组合优于继承:这里farrow_graph不是继承自graph,而是作为成员对象。这符合"组合优于继承"的设计原则,使得farrow_graph可以在不同的图容器中被复用。

5.2 内存所有权模型(C++分析)

根据C++分析指南,我们检查资源管理策略:

5.2.1 PLIO对象的所有权

// PLIO对象的创建与所有权转移
sig_i[0] = input_plio::create("PLIO_i_0", plio_64_bits, "data/sig_i.txt");

所有权分析

方面 分析
分配者 input_plio::create() 静态工厂方法(ADF框架内部实现)
所有者 std::array容器(sig_i成员变量)持有对象,生命周期与dut_graph绑定
借用者 connect<stream>() 函数临时借用PLIO的端口引用建立连接
释放策略 ADF框架管理底层资源,dut_graph析构时自动清理

关键洞察:这里的input_plio不是裸指针,而是**RAII(Resource Acquisition Is Initialization)**风格的句柄类。赋值操作转移或共享底层资源所有权,无需手动delete

5.2.2 farrow_graph成员对象

class dut_graph : public graph {
  farrow_graph farrow;  // 值语义成员对象
  // ...
};

生命周期管理

  • 分配:当dut_graph实例化时(dut_graph aie_dut;),farrow成员自动构造
  • 所有权dut_graph完全拥有farrow对象,遵循值语义
  • 释放:当aie_dut离开作用域(main函数结束),析构函数自动调用,farrow成员按声明顺序逆序销毁

内存布局假设(典型的ADF应用):

dut_graph对象内存布局(概念性):
┌─────────────────────────────┐
│ [adf::graph基类子对象]      │  ← ADF框架内部状态
├─────────────────────────────┤
│ farrow_graph farrow;        │  ← 核心算法图实例
│   - 内部kernel网络          │
│   - 内部连接拓扑              │
├─────────────────────────────┤
│ std::array<input_plio,1>    │  ← PLIO接口数组
│   sig_i;                    │
│ std::array<input_plio,1>    │
│   del_i;                    │
│ std::array<output_plio,1>   │
│   sig_o;                    │
└─────────────────────────────┘

5.3 条件编译的内存与行为差异

#ifdef AIE_SIM_ONLY不仅影响行为,也影响内存占用

模式 内存影响 行为差异
AIE_SIM_ONLY定义 PLIO对象内部存储文件路径字符串("data/sig_i.txt"),占用额外内存 从文本文件读取/写入数据,便于离线验证
AIE_SIM_ONLY未定义 PLIO对象不存储文件路径,内存占用更小 与真实PL逻辑通过物理信号交互

关键洞察:这种编译期多态(compile-time polymorphism)确保了同一套源代码可以无缝切换仿真与硬件环境,而零运行时开销(zero runtime overhead)——未使用的代码路径在编译阶段就被完全剔除。


六、设计权衡与决策深度分析

6.1 四层递进优化的设计理念

四个变体构成的优化梯度体现了硬件加速设计的经典方法论:

功能正确性 → 内存优化 → 并行优化 → 生产部署
     ↑              ↑            ↑            ↑
  initial      opt_1        opt_2       final_aie
  (基线)       (内存)        (并行)       (综合)

每层优化的关注点

阶段 核心问题 典型技术 验证指标
initial "它能否工作?" 直接映射C算法到AIE kernel 输出与参考模型一致
opt_1 "数据在哪里?" 缓存优化、数据排布、乒乓缓冲 内存访问延迟降低
opt_2 "谁在等待?" 循环展开、向量化、双缓冲 吞吐量(samples/cycle)提升
final "能否量产?" 面积优化、功耗调优、鲁棒性 PPA(性能-功耗-面积)综合指标

关键洞察:这种递进式暴露复杂度的设计允许学习者:

  1. 从可理解的基础版本开始(initial)
  2. 每次只引入一个维度的优化(opt_1→opt_2→final)
  3. 通过版本对比理解每项优化的影响

6.2 为什么保持dut_graph不变?

观察到的现象:四个变体的farrow_app.cpp内容几乎完全相同。

设计决策分析

备选方案 实现方式 优缺点
A: 每个变体独立完整实现 (未采用) 每个目录有自己的dut_graph定义 重复代码,维护困难,但完全独立
B: 共享基类/模板 (未采用) 用C++模板参数化变体差异 减少重复,但增加抽象复杂度
C: 实际采用方案 - 包装器稳定 dut_graph保持稳定,差异下沉到farrow_graph 测试接口稳定,真正的优化在内部

核心洞察:采用方案C是因为:

  1. 测试稳定性:验证环境(输入文件格式、PLIO配置、运行脚本)在优化演进中保持不变,确保性能对比的公平性
  2. 关注点分离dut_graph解决"如何与外部世界交互",farrow_graph解决"如何高效计算Farrow滤波"
  3. 教学清晰性:学习者可以专注于farrow_graph的变化,而不被测试基础设施的变动分散注意力

6.3 PLIO位宽选择的工程考量

// 统一使用64位位宽
sig_i[0] = input_plio::create("PLIO_i_0", plio_64_bits, ...);

为什么是64位?

位宽选项 适用场景 本模块选择分析
plio_32_bits 单精度浮点或32位整型,带宽需求低 不足:Farrow滤波通常需要高精度的系数乘法
plio_64_bits 双精度浮点或64位整型,高带宽 选中:平衡精度与带宽,64位可承载两个32位采样或一个高精度采样
plio_128_bits 超宽总线,极高带宽 过度:增加PL侧布线复杂度,可能超出Farrow处理的实际需求

技术细节

  • AIE-ML的PLIO支持64位或128位AXI-Stream接口
  • 选择plio_64_bits意味着PL与AIE之间的物理数据通路宽度为64位
  • 如果内部处理的是cint16(32位复数),一个64位传输可携带两个采样,提高总线利用率

七、新贡献者指南:需要警惕的陷阱

7.1 隐式契约与前置条件

使用或修改本模块时,以下隐式契约必须被遵守:

文件路径契约(仅仿真模式)

// 当前代码中的硬编码路径
sig_i[0] = input_plio::create(..., "data/sig_i.txt");
del_i[0] = input_plio::create(..., "data/del_i_optimized.txt");
sig_o[0] = output_plio::create(..., "data/sig_o.txt");

契约条款

  1. 路径结构:仿真期望在可执行文件的工作目录存在data/子目录
  2. 输入文件必须存在sig_i.txtdel_i_optimized.txt必须预先准备,否则仿真启动时崩溃
  3. 输出文件自动生成sig_o.txt由框架创建/覆盖,无需预先存在
  4. 命名约定:文件名硬编码在源代码中,修改文件名必须同步修改代码并重新编译

违反后果

  • 运行时文件打开失败,AIE仿真器抛出异常并终止
  • 错误信息可能延迟到图初始化阶段才暴露

数据格式契约

输入文本文件的格式必须严格符合AIE仿真器期望:

# sig_i.txt 期望格式(每行一个采样,十六进制或十进制)
0x0001
0x0002
0x0003
...

# 或十进制格式
1
2
3
...

陷阱警告

  • 不要添加CSV头部或其他注释行(除非仿真器文档明确支持)
  • 采样数据类型(int16/cint16/float)必须与farrow_graph内部kernel期望的类型匹配
  • 文件行数不足时,仿真可能挂起(等待更多数据)或使用零填充(取决于配置)

7.2 常见错误模式

错误模式1:混淆仿真模式与硬件模式

// 错误:在硬件构建中未移除AIE_SIM_ONLY定义
#define AIE_SIM_ONLY  // 不应该在这里定义!

// 后果:PLIO创建时尝试打开本地文件,在硬件平台上失败

正确做法AIE_SIM_ONLY应该仅在仿真构建的命令行或Makefile中定义,永远不要硬编码在源代码中。

错误模式2:修改端口连接但忘记更新数组索引

// 原始代码:单输入
connect<stream>(sig_i[0].out[0], farrow.sig_i[0]);

// 错误尝试:扩展到双输入但忘记修改数组大小
std::array<input_plio,1> sig_i;  // 仍然是大小1!
// ... 稍后 ...
connect<stream>(sig_i[1].out[0], farrow.sig_i[1]);  // 越界访问!

防御措施

  • 修改连接时同时检查数组声明和连接代码
  • 考虑使用static_assert验证数组大小与连接数量一致

错误模式3:假设PLIO名称仅用于文档

// PLIO_i_0这个名称不只是注释,它在系统中唯一标识这个接口
sig_i[0] = input_plio::create("PLIO_i_0", plio_64_bits, ...);

重要:PLIO名称在以下场景至关重要:

  • 硬件集成时,Vivado的IPI(IP Integrator)使用这些名称建立PL与AIE的连接
  • 仿真时,波形调试器使用这些名称标注信号
  • 性能分析工具使用这些名称报告带宽和延迟

命名约定建议

  • 使用描述性前缀如PLIO_i_(input)和PLIO_o_(output)
  • 使用数字后缀区分多个同类接口(_0, _1, ...)
  • 在团队协作中维护命名规范文档

7.3 调试技巧与故障排查

仿真模式调试清单

当仿真行为不符合预期时,按以下顺序检查:

  1. 输入文件检查

    # 确认文件存在且非空
    ls -la data/sig_i.txt data/del_i_optimized.txt
    wc -l data/sig_i.txt  # 检查行数是否足够
    head -5 data/sig_i.txt  # 查看格式是否正确
    
  2. 编译标志检查

    # 确认AIE_SIM_ONLY已定义
    grep -r "AIE_SIM_ONLY" Makefile build/
    
  3. 输出文件分析

    # 检查输出是否生成
    ls -la data/sig_o.txt
    head -20 data/sig_o.txt  # 查看输出格式
    
  4. 波形调试(高级):

    • aie_dut.run(4)前添加断点
    • 使用Xilinx Vitis Analyzer查看AIE核的执行波形
    • 检查PLIO端口的数据传输时序

硬件模式集成检查

当迁移到真实硬件时:

  1. 移除AIE_SIM_ONLY定义:确保构建系统没有在硬件目标中定义此宏
  2. Vivado连接检查:在IPI设计中确认PLIO名称与AXI-Stream接口正确连接
  3. 时序约束:确认PL到AIE的时钟域 crossing 已正确处理
  4. DMA配置:如果使用DMA搬运数据,检查buffer descriptor设置

八、与其他模块的关系

8.1 模块依赖图

flowchart TB subgraph "本模块内部" FPI[farrow_port_initial] FO1[farrow_opt_1] FO2[farrow_opt_2] FFA[farrow_final_aie] end subgraph "AMD Vitis 平台层" VEV[Vitis_Export_To_Vivado
Makefile.graph] end subgraph "父模块/教程上下文" FFT[06-farrow_filter
教程主模块] end %% 内部演进关系 FPI -.->|"opt_1 优化"| FO1 FO1 -.->|"opt_2 优化"| FO2 FO2 -.->|"最终优化"| FFA %% 外部依赖 FPI -.->|depends_on| VEV FO1 -.->|depends_on| VEV FO2 -.->|depends_on| VEV FFA -.->|depends_on| VEV %% 父模块包含 FFT -.->|contains| FPI FFT -.->|contains| FO1 FFT -.->|contains| FO2 FFT -.->|contains| FFA

8.2 关键外部依赖

Vitis_Export_To_Vivado.Makefile.graph

所有四个变体都依赖于Vitis_Platform_Creation.Feature_Tutorials.03_Vitis_Export_To_Vivado.Makefile.graph。这是一个构建系统依赖,而非运行时依赖。

依赖内容

  • 包含用于构建AIE图的通用Makefile规则
  • 定义了仿真、综合、实现的编译流程
  • 提供Vivado集成的标准接口

对开发者的意义

  • 修改本模块代码时,需要理解外部Makefile定义的构建规则
  • 添加新的编译选项可能需要修改父目录的Makefile或本目录的配置

8.3 在教程体系中的位置

本模块是06-farrow_filter设计教程的核心组成部分,位于以下知识路径中:

AIE_ML_Design_Graphs
└── farrow_filter_design_variants (本模块)
    ├── farrow_port_initial (基线)
    ├── farrow_opt_1 (优化1)
    ├── farrow_opt_2 (优化2)
    └── farrow_final_aie (最终版)

相关模块参考

相关模块 关系类型 参考目的
prime_factor_fft_pipeline_graphs 平行参考 对比不同信号处理算法的AIE实现模式
channelizer_ifft_and_tdm_fir_graphs 平行参考 学习其他多相滤波结构的实现
farrow_filter_streaming_io_integration 下游依赖 理解本模块如何集成到完整系统中

九、总结与最佳实践

9.1 设计模式总结

本模块展示了以下可复用的AIE设计模式:

模式1:包装器稳定模式(Wrapper Stability Pattern)

  • 问题:算法优化演进时,如何保持测试基础设施不变?
  • 方案dut_graph作为稳定包装器,farrow_graph作为可变实现
  • 收益:优化迭代不影响测试代码,保证性能对比公平性

模式2:双模式编译模式(Dual-Mode Compilation Pattern)

  • 问题:同一套代码如何同时支持软件仿真和硬件运行?
  • 方案AIE_SIM_ONLY条件编译隔离两种行为
  • 收益:零运行时开销的可移植性,开发效率最大化

模式3:未来可扩展数组模式(Future-Proof Array Pattern)

  • 问题:当前单通道实现,如何为未来多通道预留空间?
  • 方案std::array<T,1>包装单对象,改为N即可扩展
  • 收益:编译期确定性与运行期灵活性的平衡

9.2 新贡献者行动指南

如果你需要:

任务 建议步骤 注意事项
添加新的优化变体 1. 复制farrow_final_aie目录
2. 重命名为farrow_opt_3
3. 修改farrow_graph.h实现新优化
4. 保持farrow_app.cpp不变
不要修改dut_graph代码,只修改farrow_graph
调试仿真失败 1. 检查data/目录存在且文件非空
2. 确认AIE_SIM_ONLY在编译时定义
3. 查看sig_o.txt输出格式
4. 使用Vitis Analyzer查看波形
输入文件路径是相对于工作目录的相对路径
移植到硬件 1. 确保未定义AIE_SIM_ONLY
2. 在Vivado IPI中连接PLIO到AXI-Stream接口
3. 配置DMA或PL逻辑提供/消费数据
4. 综合实现并生成bitstream
PLIO名称在Vivado中用于自动连接匹配
优化farrow_graph 1. 分析当前实现的瓶颈(内存延迟/计算并行度)
2. 参考AMD AIE优化指南
3. 修改kernel代码(不在本模块展示的部分)
4. 对比性能指标验证优化效果
dut_graph保持不变,确保对比公平

9.3 最终思考

本模块的价值不仅在于展示一个Farrow滤波器的AIE实现,更在于它演示了硬件加速设计的系统方法论

  1. 分层抽象:从物理接口到算法实现,每层职责清晰
  2. 渐进优化:功能正确性优先,逐步引入性能优化
  3. 可移植设计:同一套代码跨越仿真到硬件的鸿沟
  4. 教学友好:包装器稳定模式让学习者聚焦于核心优化

理解这个模块的设计哲学,将帮助你不仅使用这些代码,更能设计出自己的高性能AIE应用。


文档版本:1.0
最后更新:基于AI_Engine_Development/AIE-ML/Design_Tutorials/06-farrow_filter设计
维护者:新团队成员参考文档

On this page