第三章:FINN项目结构解析
好,咱们直接进入正题。这一章我带你拆解FINN的项目结构。说白了,就是搞清楚这个框架的「骨架」长什么样。我在刚开始接触FINN时,第一反应就是——这目录怎么这么多?别慌,我帮你理清楚。
3.1 源码目录结构
FINN的源码目录,我习惯把它分成三大块:核心库、编译工具、以及示例。你从GitHub拉下来后,会看到这样的结构:
finn/
├── finn-core/ # 核心库,别乱动
├── finn-experimental/ # 实验性功能,慎用
├── finn-hlslib/ # 高层次综合库
├── finn-rtllib/ # 运行时库
├── finn-quick-tests/ # 快速测试
├── docs/ # 文档
├── examples/ # 示例项目
└── setup.py # 安装脚本
嗯,这里要注意:finn-core 是心脏,你自定义算子主要跟它打交道。我见过不少新手直接改核心库的代码,结果一更新就全乱了。我的建议是——核心库只读,别动。
3.1.1 finn-core 内部结构
咱们重点看 finn-core 里面长什么样:
finn-core/
├── src/
│ ├── finn/
│ │ ├── core/ # 核心数据流图
│ │ ├── transformation/ # 图变换
│ │ ├── custom_op/ # 自定义算子目录
│ │ ├── builder/ # 构建器
│ │ └── util/ # 工具函数
│ ├── tests/ # 单元测试
│ └── examples/ # 使用示例
├── include/
│ └── finn/
│ └── custom_op/ # 自定义算子头文件
└── CMakeLists.txt # 构建配置
我个人习惯把自定义算子放在 custom_op/ 目录下。为什么?因为FINN的构建系统会自动扫描这个目录。你想想看,如果随便放个地方,构建时找不到,那多尴尬。
3.2 关键配置文件说明
FINN的配置,说白了就是几个YAML和JSON文件。我刚开始时被这些文件搞得头大,后来发现其实套路很固定。
3.2.1 核心配置文件
| 文件名 | 位置 | 作用 |
|---|---|---|
finn_config.yaml |
项目根目录 | 全局配置,比如目标平台、精度设置 |
custom_op_config.json |
custom_op/ 下 |
自定义算子的注册信息 |
build_config.cmake |
build/ 目录 |
构建参数,比如是否启用调试 |
举个例子,custom_op_config.json 长这样:
{
"custom_ops": [
{
"name": "my_conv2d",
"type": "hls",
"source": "src/my_conv2d.cpp",
"header": "include/my_conv2d.h",
"clock_period": 10,
"target": "xczu9eg-ffvb1156-2-i"
}
]
}
这里有个坑:clock_period 的单位是纳秒。我曾经设成10,结果综合出来时序不满足,折腾了两天才发现是单位搞错了。嗯,细节决定成败。
3.3 构建系统介绍
FINN用的是CMake + Make的构建系统。为什么不用别的?说白了,FPGA工具链(Vivado、Vitis)都支持CMake,兼容性最好。
3.3.1 构建流程
构建流程其实就三步:
- 配置:
cmake -B build -DCMAKE_BUILD_TYPE=Release - 编译:
cmake --build build --target finn_core - 安装:
cmake --install build --prefix /usr/local
我建议你第一次构建时加上 -DFINN_DEBUG=ON,这样能看到详细的编译日志。遇到问题也好排查。
3.3.2 自定义算子的构建集成
当你添加了自定义算子后,需要在 CMakeLists.txt 里注册一下。具体做法是:
# 在 finn-core/CMakeLists.txt 中添加
add_custom_op(my_conv2d
SOURCES src/custom_op/my_conv2d.cpp
HEADERS include/custom_op/my_conv2d.h
DEPENDS hls_lib
)
为什么要加 DEPENDS hls_lib?因为你的自定义算子很可能依赖HLS库。我遇到过有人忘了加这个依赖,结果编译时一堆未定义引用错误。
3.4 自定义算子存放位置
好,这是重点中的重点。自定义算子到底放哪?
3.4.1 推荐目录结构
我个人习惯这样组织:
finn-core/
├── src/
│ └── custom_op/
│ ├── my_conv2d.cpp # 实现文件
│ ├── my_pool.cpp # 另一个算子
│ └── CMakeLists.txt # 算子级构建
├── include/
│ └── finn/
│ └── custom_op/
│ ├── my_conv2d.h # 头文件
│ └── my_pool.h
└── tests/
└── custom_op/
├── test_my_conv2d.cpp # 单元测试
└── test_my_pool.cpp
你想想看,这样分开放的好处是什么?解耦。每个算子独立编译,互不影响。我曾经见过有人把所有算子写在一个文件里,结果改一个就要重新编译全部,那叫一个慢。
3.4.2 避坑指南
⚠️ 注意:
- 不要把你的算子放在
finn-core/src/finn/core/下,那是核心逻辑,不是给你放算子的 - 头文件路径要跟
#include一致,比如#include "finn/custom_op/my_conv2d.h" - 记得在
custom_op_config.json里注册,否则FINN找不到你的算子
💡 我的小技巧:
每次添加新算子后,先跑一下 finn-quick-tests 里的测试。如果测试通过,说明你的算子被正确集成了。如果失败,检查一下配置文件和路径。
3.5 实战:添加一个简单的自定义算子
光说不练假把式。咱们来走一遍流程。
3.5.1 步骤一:创建文件
# 在 finn-core/src/custom_op/ 下创建
touch my_add.cpp
# 在 finn-core/include/finn/custom_op/ 下创建
touch my_add.h
3.5.2 步骤二:编写代码
my_add.h:
#ifndef MY_ADD_H
#define MY_ADD_H
#include "ap_int.h"
#include "hls_stream.h"
void my_add(
hls::stream<ap_uint<16>> &in1,
hls::stream<ap_uint<16>> &in2,
hls::stream<ap_uint<16>> &out,
int num_elements
);
#endif
my_add.cpp:
#include "my_add.h"
void my_add(
hls::stream<ap_uint<16>> &in1,
hls::stream<ap_uint<16>> &in2,
hls::stream<ap_uint<16>> &out,
int num_elements
) {
#pragma HLS INTERFACE axis port=in1
#pragma HLS INTERFACE axis port=in2
#pragma HLS INTERFACE axis port=out
#pragma HLS INTERFACE s_axilite port=num_elements
for (int i = 0; i < num_elements; i++) {
#pragma HLS PIPELINE II=1
ap_uint<16> a = in1.read();
ap_uint<16> b = in2.read();
out.write(a + b);
}
}
3.5.3 步骤三:注册算子
在 custom_op_config.json 里添加:
{
"name": "my_add",
"type": "hls",
"source": "src/custom_op/my_add.cpp",
"header": "include/finn/custom_op/my_add.h",
"clock_period": 10,
"target": "xczu9eg-ffvb1156-2-i"
}
3.5.4 步骤四:构建测试
cd finn
cmake -B build -DFINN_DEBUG=ON
cmake --build build --target finn_core
# 如果构建成功,说明你的算子被正确集成了
嗯,到这里,你的第一个自定义算子就集成进去了。是不是比想象中简单?
📌 本章要点回顾:
- FINN源码目录:核心库在
finn-core,自定义算子放custom_op/ - 关键配置:
custom_op_config.json是算子的「身份证」 - 构建系统:CMake + Make,记得加
DEPENDS hls_lib - 存放位置:源文件在
src/custom_op/,头文件在include/finn/custom_op/
下一章,咱们会深入自定义算子的接口规范。说白了,就是告诉FINN你的算子长什么样、怎么用。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。