stage1 VIT加速器部署多层block
了解模块
1、HLS代码模块与模块连接可能出现隐式截断,比如说两个15bit位宽连接到AXI_STREAM中会扩展到32位,但是连接下面的14bit位宽会截断高位。
解决方法:添加内部数据流接口,添加tlast信号。
添加tlast:
for(int i = 0; i < OUTPUT_WORDS; i++) {
#pragma HLS PIPELINE II=1
hls::vector<mlp1_of_t, TP*COAP> vec = internal_out.read();
axis_pkt pkt;
ap_uint<32> raw = 0;
// vec[0] 放在低 13 位
// vec[1] 放在高 13 位
// 注意:这里是紧凑排列
raw(12, 0) = vec[0];
raw(25, 13) = vec[1];
pkt.data = raw;
// 设置 AXI Stream 辅助信号
pkt.keep = -1; // 全1,表示所有字节有效 (0xF)
pkt.strb = -1;
// 【核心修复】生成 TLAST 信号
// 只有最后一个包 (18815) 才拉高 last
if (i == OUTPUT_WORDS - 1) {
pkt.last = 1;
} else {
pkt.last = 0;
}
network_out.write(pkt);
}
2、出现Error in llvm-link错误原因:
全局变量问题:const int是只在当前文件起作用,其他文件包含该文件头文件时,引用需要加extern,而int函数是全局变量。const全局变量使得我们指定了一个语义约束,即被修饰的全局变量不允许被修改。
在多个文件域中,尽量避免使用int,且避免int定义变量名字重复。因为int定义变量全局可访问。
解决方法:把相同名字的变量名字更改,或者使用static const int代替const int,static const int只作用于当前文件。
3、出现Impl=uram in bind_storage is not supported for the targeted device报错
Impl=uram in bind_storage is not supported for the targeted device xczu9eg-ffvb1156-2-e报错需要在patch_embed.h文件中更改impl=URAM latency=3变成impl=BRAM,因为ZCU102 开发板不支持 UltraRAM (URAM)。
BRAM (Block RAM)
- 这是什么:FPGA 里的标准嵌入式存储块。
- 规格:通常是 18Kb 或 36Kb 一个块。
- 特点:
- 灵活:可以配置成各种位宽(如 18x1024 或 36x512)。
- 支持初始化:关键点! BRAM 的内容是 FPGA 比特流(Bitstream)配置的一部分。上电后加载比特流初始化,BRAM 里就可以有预设的值。类似于ROM掉电不丢失。 但是URAM 没有连接到全局配置逻辑的电路接口。比特流加载器“够不着” URAM 的内部单元。所以不能初始化。
- 双端口:支持真正的读写同时进行。
- 用途:小缓存、FIFO、存小系数表
URAM (Ultra RAM)
- 这是什么:Xilinx UltraScale+ 系列(如 ZU9EG)引入的更大块的存储。
- 规格:单个块高达 288Kb (是 BRAM 的 8 倍)。
- 特点:
- 密度高:专门用来存大量数据,为了节省芯片面积,去掉了 BRAM 的一些灵活性。
- 不支持初始化:最关键点! URAM 没有连接到 FPGA 的全局配置电路。上电时,URAM 里的数据是空的(或随机的)。 它只能当 RAM 用,不能像 BRAM 那样当 ROM 用(除非有加载电路)。ram和rom区别主要在于rom掉电不丢失,通常只可读,而ram可读可写。
- 级联强:非常适合把很多块连起来做一个超大的 Buffer。
- 用途:片上大缓存(用来代替片外 DDR 存储中间特征图)
XCZU9EG 提供了丰富的其他类型的片上存储资源,包括:
- Block RAM (BRAM):总块 RAM 容量:32.1 Mb(即约 4.01 MB)
- Distributed RAM:约 8.8 Mb。
- 板载外部存储:ZCU102 开发板还提供了大量的外部存储,包括 4GB 的 PS 端 DDR4 SODIMM 和 512MB 的 PL 端 DDR4 组件内存。(PL端内存是4Gb,也就是512MB(4Gb ÷ 8 = 512MB)。)
4、有关 #pragma HLS dataflow
当编译器看到 dataflow 时,它不再生成一个巨大的单一状态机来依次执行 atten0 -> mlp0 -> atten1,而是增加了任务调度:
- 调度器会同时给所有内部模块(PatchEmbed, atten0, mlp0...)发送 ap_start 信号。所有模块同时处于“待命”状态,只要数据来了就执行数据处理。比如一旦 stream_pe_to_attn0 里有数据,atten0 就可以开始工作,完全不需要等待 PatchEmbed 全部处理完。
ap_ctrl_chain的握手与反压:假设 mlp0 处理得慢,输入 FIFO 满了。mlp0 的 ap_ready 会变低(或者通过 FIFO 的 full 信号反馈)。- dataflow 的性能受 FIFO 深度影响。
- 与
#pragma HLS pipeline区别,pipeline是在for等函数内部展开循环,并行处理,而dataflow是在函数外部,多个函数展开循环,并行调度。
全局变量全局常量和静态常量
// ================= 全局区域 =================
int global_int = 1; // 全局变量,所有文件可见
const int global_const = 2; // 全局常量(C++默认内部链接)
static int file_static = 3; // 文件静态,仅本文件可见
// ================= 函数内部 =================
void test_function() {
int local_int = 10; // 局部变量
const int local_const = 20; // 局部常量
static int func_static = 30; // 函数静态变量
// ❌ 这些都不是全局变量!
// 只是具有不同的存储特性和生命周期
}
5、问题:每个IP模块都有ap_ctrl_chain,但是下面在显式连接时只看到了以下单纯Axi_stream接口,难道不用考虑ap_ctrl_chain吗?
patch_embed_top(network_in, s_pe_attn0);
// 2. Attn0 (输入13b, 输出15b)
atten0(s_pe_attn0, s_attn0_mlp0);
// 3. MLP0 (输入15b, 输出13b)
mlp0(s_attn0_mlp0, network_out);
解答:
ap_ctrl_chain是硬件层面的握手协议信号(包含 ap_start, ap_done, ap_ready, ap_continue),它在 C++ 语言层面是隐形的。HLS 编译器看到顶层的#pragma HLS dataflow,它会自动生成一个调度器(Scheduler或有限状态机(FSM)。这个生成的调度器会自动连接两个模块的 ap_start、ap_done 和 ap_continue 信号。在 Dataflow 设计中,ap_ctrl_chain比默认的ap_ctrl_hs更好。ap_ctrl_hs (Handshake): 只有 Start 和 Done。上一级做完了,下一级才开始。#pragma HLS STREAM variable=link_attn1_mlp1 depth=128HLS-Stream本质是一个fifo,可以自定义模块与模块之间的缓冲FIFO深度。
本文来自博客园,作者:{ziyang Li},转载请注明原文链接:https://www.cnblogs.com/ziy123/p/19455986

浙公网安备 33010602011771号