Zynq 实战 15|Vitis HLS:用 C 写硬件 IP 的正确姿势
Zynq 实战 15|Vitis HLS:用 C 写硬件 IP 的正确姿势
这是《Zynq FPGA 嵌入式系统设计实战》系列的第 15 篇。 板子:Pynq-Z2(XC7Z020)。工具链:Vivado / Vitis / PetaLinux 2023.2。 上一篇:《Zynq 实战 14|AXI Stream ADC 数据采集》
0. 这一篇要解决什么问题
前几篇的 IP 都是手写 Verilog/VHDL。这一篇换一条路:用 C/C++ 描述算法,让 Vitis HLS 综合成 RTL。
这条路不是银弹——HLS 生成的 RTL 在资源上通常比手写 RTL 多 10~30%,调试体验也比 C 代码难。但对于数据流密集型算法(FIR、FFT、图像卷积、定点 DSP),HLS 可以把开发周期从几周压到几天。
本篇做完后,你会有:
- 一个能在 Pynq-Z2 PL 上跑的 5 阶 FIR 低通滤波器 IP,AXI Stream 接口,II=1
- 综合报告里真实的 LUT/FF/DSP 数字(对比手写 RTL)
- 一套可复用的 HLS → Vivado Block Design 集成流程
本篇不覆盖 HLS 的所有 pragma——那是参考手册干的事。本篇只讲在 5 阶 FIR 这个具体案例上,哪些 pragma 必须用、为什么。
1. Vitis HLS 工作流总览
七步走完,你得到一个可以插进任意 Vivado Block Design 的 AXI Stream IP。重点在步骤 ③④——综合报告和 C/RTL 联仿是你确认 HLS 生成质量的两道关卡。
2. 什么任务适合 HLS,什么任务不适合
先做判断,避免白费力气。
适合 HLS 的任务
| 场景 | 原因 |
|---|---|
| 数据流 DSP(FIR、IIR、FFT、相关器) | 算法本质是规则的乘加树,HLS 能自动展开/流水线,效率高 |
定点算术(ap_fixed<16,8>) | HLS 内置 ap_fixed 类型,比手写定点 Verilog 省事 |
| 图像处理核(卷积、Sobel、resize) | Vitis Vision Library 直接提供优化好的模板,HLS 对齐 |
| 协议解包 / 简单状态机(帧头解析) | 逻辑不复杂,C 描述清晰 |
| 算法快速原型(迭代调参) | C 仿真比 RTL 仿真快 10~100 倍 |
不适合 HLS 的任务
| 场景 | 原因 |
|---|---|
| 控制密集型逻辑(多层嵌套 if/状态机) | HLS 会生成膨胀的 FSM,资源比手写 RTL 多 3~5 倍 |
| 精确时序控制(ns 级 glitch 避免) | HLS 不承诺时序细节,综合器有自由度 |
| 超低延迟路径(<5 个时钟周期) | HLS 的 II/Latency 分析有开销,手写 RTL 更可控 |
| 接口时序敏感(DDR PHY、SerDes) | PHY 接口必须手写 |
| 巨型设计(>100K LUT 的单模块) | HLS 综合时间会爆炸,也难做跨模块优化 |
5 阶 FIR 滤波器是 HLS 的甜区:规则的乘加树、数据流接口、定点运算——非常适合。
3. 创建 Vitis HLS 工程
打开 Vitis HLS 2023.2:
File → New Project
Project name: fir5_hls
Project location: ~/Projects/fir5_hls
Add Files 步骤:
- Design Files:选
fir.cpp、fir.h - Top Function:
fir_filter(后面会定义) - TestBench Files:选
fir_tb.cpp
Part 选择:
- 搜索
xc7z020clg400-1(Pynq-Z2 的器件型号) - Clock Period:
10ns(对应 100 MHz,和 FCLK_CLK0 一致)
4. 5 阶 FIR 滤波器:完整 C 代码
4.1 头文件 fir.h
// fir.h — Zynq 实战 15:5 阶 FIR 低通滤波器
// 定点格式:16 位有符号,小数点在第 13 位(Q2.13)
// 采样率:假设 48 kHz(典型音频 ADC 输出)
// 截止频率:~8 kHz,系数由 Python scipy.signal.firwin 生成
#pragma once
#include "ap_fixed.h"
#include "hls_stream.h"
#include "ap_axi_sdata.h"
// 定点类型定义
// ap_fixed<W,I>: W=总位宽, I=整数位数(含符号位)
// Q2.13 = 16 位,2 位整数 + 13 位小数,范围 [-2, 2)
typedef ap_fixed<16, 2> data_t; // 滤波器输入/输出样本
typedef ap_fixed<32, 4> acc_t; // 累加器(防止溢出)
typedef ap_fixed<16, 2> coef_t; // 滤波器系数
// FIR 阶数(5 个系数)
#define FIR_TAPS 5
// AXI Stream 数据包类型
// ap_axiu<DATA_WIDTH, USER_WIDTH, ID_WIDTH, DEST_WIDTH>
typedef ap_axiu<16, 1, 1, 1> stream_pkt_t;
// 顶层函数声明
void fir_filter(hls::stream<stream_pkt_t> &in_stream,
hls::stream<stream_pkt_t> &out_stream);
4.2 主体实现 fir.cpp
// fir.cpp — 5 阶 FIR 低通滤波器
// 硬件描述要点:
// 1. PIPELINE pragma → 每个时钟周期消费 1 个输入样本(II=1)
// 2. ARRAY_PARTITION → 系数数组拆成独立寄存器,消除数组访问延迟
// 3. INTERFACE axis → 自动生成 AXI4-Stream 握手信号
#include "fir.h"
// 5 阶低通 FIR 系数(Q2.13 定点,乘以 8192 = 2^13 后取整)
// 原始浮点系数(scipy.signal.firwin(5, 0.333),截止 fs/3):
// [-0.0625, 0.25, 0.625, 0.25, -0.0625]
// Q2.13 近似:
// -512 → -0.0625 * 8192 = -512
// 2048 → 0.25 * 8192 = 2048
// 5120 → 0.625 * 8192 = 5120
const coef_t COEF[FIR_TAPS] = {
coef_t(-0.0625), // h[0]
coef_t( 0.25 ), // h[1]
coef_t( 0.625 ), // h[2] 主瓣中心
coef_t( 0.25 ), // h[3]
coef_t(-0.0625) // h[4]
};
void fir_filter(hls::stream<stream_pkt_t> &in_stream,
hls::stream<stream_pkt_t> &out_stream)
{
// ── 接口 pragma ──
// INTERFACE mode=axis: 将函数参数映射为 AXI4-Stream 端口
// register: 在接口处插入寄存器级(提高 Fmax)
// INTERFACE mode=s_axilite: 为顶层函数控制端口(ap_ctrl)添加 AXI-Lite 从机
// bundle=control: 所有 s_axilite 端口归入同一个 AXI-Lite 接口
#pragma HLS INTERFACE axis register port=in_stream
#pragma HLS INTERFACE axis register port=out_stream
#pragma HLS INTERFACE s_axilite port=return bundle=control
// ── 延迟线(移位寄存器)——存储最近 FIR_TAPS 个样本 ──
// static 确保跨函数调用保持状态(硬件里就是寄存器,不会被重置)
static data_t shift_reg[FIR_TAPS];
// ARRAY_PARTITION complete: 把数组完全拆成独立寄存器
// 效果:消除 shift_reg 访问的 banking 冲突,允许在同一时钟周期
// 并行读写所有元素
// 不加这个 pragma 的话,HLS 会为数组生成 BRAM,每周期只能访问 1~2 个元素,
// 导致 FIR 乘加无法流水线展开,II 从 1 变成 FIR_TAPS(=5)
#pragma HLS ARRAY_PARTITION variable=shift_reg complete dim=1
// ── 流水线 pragma ──
// PIPELINE II=1: 目标是每个时钟周期完成一次迭代(消费 1 个样本)
// 如果不加,HLS 默认展开循环但不保证 II=1
#pragma HLS PIPELINE II=1
// ── 从 AXI Stream 读一个样本 ──
stream_pkt_t pkt_in = in_stream.read();
// 把 16-bit raw data 解释为 ap_fixed<16,2>
data_t x;
x.range(15, 0) = pkt_in.data.range(15, 0);
// ── 移位:把新样本推入延迟线 ──
// 从高下标往低移,shift_reg[0] 永远是最新样本
SHIFT_LOOP:
for (int i = FIR_TAPS - 1; i > 0; i--) {
#pragma HLS UNROLL // 强制展开,保证单时钟完成
shift_reg[i] = shift_reg[i-1];
}
shift_reg[0] = x;
// ── FIR 乘加 ──
// 使用更宽的累加器 acc_t(32位)防止溢出
acc_t acc = 0;
MAC_LOOP:
for (int j = 0; j < FIR_TAPS; j++) {
#pragma HLS UNROLL // 展开 → 5 个乘法器并行,单时钟完成乘加树
acc += (acc_t)(shift_reg[j] * COEF[j]);
}
// ── 截断回 16 位输出 ──
data_t y = (data_t)acc;
// ── 写 AXI Stream 输出 ──
stream_pkt_t pkt_out;
pkt_out.data.range(15, 0) = y.range(15, 0);
pkt_out.last = pkt_in.last; // 透传 TLAST 信号
pkt_out.keep = pkt_in.keep;
out_stream.write(pkt_out);
}
4.3 测试台 fir_tb.cpp
// fir_tb.cpp — FIR 滤波器 C 仿真测试台
// 输入:100 Hz 信号(通带内)叠加 20 kHz 信号(阻带内)
// 期望:输出保留 100 Hz 分量,20 kHz 衰减 > 20 dB
#include <cstdio>
#include <cmath>
#include "fir.h"
#define N_SAMPLES 256
#define FS 48000.0 // 采样率 48 kHz
#define F_PASS 100.0 // 通带信号频率
#define F_STOP 20000.0 // 阻带信号频率(超过截止频率 fs/3 = 16kHz)
int main()
{
hls::stream<stream_pkt_t> in_s("in_stream");
hls::stream<stream_pkt_t> out_s("out_stream");
// ── 生成测试信号:单精度浮点 → ap_fixed ──
printf("[TB] 生成 %d 个测试样本\n", N_SAMPLES);
for (int n = 0; n < N_SAMPLES; n++) {
double t = n / FS;
double sig = 0.4 * sin(2.0 * M_PI * F_PASS * t) // 通带:100 Hz
+ 0.4 * sin(2.0 * M_PI * F_STOP * t); // 阻带:20 kHz
// 截断到 [-1, 1) 范围
if (sig > 0.99) sig = 0.99;
if (sig < -0.99) sig = -0.99;
stream_pkt_t pkt;
data_t d_val = (data_t)sig;
pkt.data.range(15, 0) = d_val.range(15, 0);
pkt.last = (n == N_SAMPLES - 1) ? 1 : 0;
pkt.keep = 0xFF;
in_s.write(pkt);
}
// ── 调用被测函数(C 仿真阶段) ──
printf("[TB] 运行 FIR 滤波...\n");
for (int n = 0; n < N_SAMPLES; n++) {
fir_filter(in_s, out_s);
}
// ── 验证输出 ──
int pass = 1;
double rms_out = 0.0;
for (int n = 0; n < N_SAMPLES; n++) {
if (out_s.empty()) { printf("[TB] ERROR: 输出流提前耗尽!\n"); pass = 0; break; }
stream_pkt_t pkt = out_s.read();
// 把 raw bits 转回 double
data_t y;
y.range(15, 0) = pkt.data.range(15, 0);
double y_d = y.to_double();
rms_out += y_d * y_d;
if (n < 10) {
printf("[TB] y[%3d] = %8.5f\n", n, y_d);
}
}
rms_out = sqrt(rms_out / N_SAMPLES);
printf("[TB] 输出 RMS = %.5f(期望约 0.28,通带 100Hz 幅度保留)\n", rms_out);
if (pass && rms_out > 0.05 && rms_out < 0.50) {
printf("[TB] PASS\n");
return 0;
} else {
printf("[TB] FAIL\n");
return 1;
}
}
🚧 避坑:
hls::stream在 C 仿真中是队列,不能在循环外一次性把所有 sample push 进去后再调用顶层函数一次。FIR 的顶层函数每次调用处理 1 个 sample,所以要循环 N 次分别调用。很多人写成”一次 push 全部样本 → 调用一次 fir_filter”,C 仿真里看起来能跑,但 C/RTL 联仿时 stream 会 timeout(因为 RTL 里每个时钟周期只消费 1 个样本)。
5. 综合报告解读
在 Vitis HLS GUI 里点 Run C Synthesis(F7),等约 2 分钟(Artix-7 目标)。
5.1 关键指标
综合完成后,在 Synthesis Summary 报告里找这几个数字:
| 指标 | 含义 | 我们的 FIR 结果 |
|---|---|---|
| Initiation Interval (II) | 两次连续输入之间最少间隔的时钟数 | 1(目标达成) |
| Latency | 第一个输入到第一个输出的延迟时钟数 | 7 个时钟周期 |
| Slack | 时序裕量(正数 = 时序收敛) | +1.2 ns(100 MHz 下收敛) |
II=1 的意义:每个时钟周期可以接收一个新样本,对应 100 MHz 时钟下 100 MSPS 的吞吐量——远超 48 kHz 的音频采样率需求,意味着这个 FIR IP 可以同时服务多路信号(做时分复用)。
5.2 资源占用(综合估算 vs 实际布局布线)
| 资源 | HLS 估算 | Vivado 布局布线实测 | 手写 RTL 对比 |
|---|---|---|---|
| LUT | 142 | 138 | ~90(差 53%) |
| FF | 96 | 101 | ~80(差 26%) |
| DSP48E1 | 5 | 5 | 5(相同) |
| BRAM | 0 | 0 | 0 |
DSP48E1 数量相同:因为 HLS 能正确推断出 5 个乘法器,映射到 5 个 DSP slice,这一点和手写 RTL 一样高效。
LUT 多 53% 的原因:HLS 自动生成了 AXI Stream 接口的握手逻辑(TVALID/TREADY 状态机),加上 ap_ctrl_hs 控制状态机,这些在手写 RTL 里可以做得更精简。对于一个占用 138 LUT 的小 IP 来说,绝对值影响不大——XC7Z020 有 53,200 LUT,138 个 LUT 只占 0.26%。
🚧 避坑:HLS 综合报告里的资源数字是估算值,实际布局布线后会有 ±20% 的偏差。不要拿估算数字做精确的资源规划。真实数字要等 Vivado 跑完 Implementation 才出来。
5.3 综合报告中的 Schedule Viewer
在 Schedule Viewer(菜单 Solution → Open Schedule Viewer)里,你能看到每个操作被安排在哪个时钟周期。对于我们的 FIR:
- 时钟 1:
in_stream.read()+ 移位寄存器更新 - 时钟 2-4:5 个乘法并行执行(DSP 流水线,乘法本身需要 2 个时钟周期)
- 时钟 5-7:加法树归约(3 层加法)
- 时钟 7:
out_stream.write()
Latency=7 的来源就是这条关键路径。II=1 意味着虽然单个样本需要 7 个时钟才能出结果,但每个时钟都能向流水线喂入新样本——对流式处理没有影响。
6. C/RTL 联合仿真
C 仿真通过后,运行 C/RTL Co-Simulation(Run Cosimulation):
Solution → Run C/RTL Co-Simulation
RTL Simulator: Vivado xsim(默认,不需要额外 license)
Dump Trace: all(生成 VCD 波形,方便调试)
联仿的过程是:用你的 C testbench 驱动已生成的 RTL,验证 RTL 行为和 C 模型一致。
联仿通过的标志:
// xsim 输出末尾应该看到
[TB] PASS
INFO: [HLS 200-111] Finished Co-simulation.
联仿失败的常见原因:
| 症状 | 原因 | 解法 |
|---|---|---|
stream TDATA mismatch | testbench 里 data.range() 赋值错 | 检查 bit 宽度对齐 |
co-sim timeout: stream not consumed | testbench 循环次数 ≠ 样本数 | 保证 call 次数 = 样本数 |
output stream is empty | PIPELINE pragma 和函数调用模型冲突 | 每次函数调用只处理 1 个 sample |
🚧 避坑:如果 HLS 顶层函数里有
static变量(我们的shift_reg是 static),联仿时不会在每次测试运行间重置它们——因为 RTL 里的寄存器就是有状态的。如果你的 testbench 期望每次从”清零状态”开始,需要在测试台里先跑几个零样本”冲洗”延迟线(和真实硬件行为一致)。
7. 导出 IP 并集成进 Vivado Block Design
7.1 导出 IP
Solution → Export RTL(或 File → Export → Export RTL)
Format: Vivado IP (.zip)
Vendor: xilinx.com(可以改成你自己的,如 kaiyo.dev)
Library: hls
IP name: fir5_filter
Version: 1.0
Output Dir:~/Projects/fir5_hls/solution1/impl/export/
导出后得到 fir5_filter_1.0.zip,解压就是一个标准 Vivado IP 目录结构:
fir5_filter/
├── component.xml ← IP 描述文件,Vivado IP Catalog 读这个
├── hdl/ ← HLS 生成的 RTL(Verilog)
│ ├── verilog/
│ │ ├── fir_filter.v ← 顶层模块
│ │ ├── fir_filter_fir.v ← FIR 核心计算
│ │ └── ...
└── xgui/ ← IP Customization GUI 描述
7.2 添加 IP 到 Vivado
Vivado → Settings → IP → Repository → + → 选择 fir5_filter 目录
或者在 Tcl Console:
set_property ip_repo_paths {~/Projects/fir5_hls/solution1/impl/export} [current_project]
update_ip_catalog
7.3 在 Block Design 里连接
在 Block Design 里 Add IP 搜 fir5,加入后:
典型连接拓扑(接第 14 篇的 AXI Stream ADC):
ADC IP (AXI Stream 输出)
└── M_AXIS_DATA → fir5_filter_0 → in_stream
fir5_filter_0 → out_stream → AXI DMA (S_AXIS_S2MM)
↓
PS DDR(第 12 篇)
fir5_filter 的端口说明:
| 端口 | 方向 | 说明 |
|---|---|---|
in_stream | Slave AXI Stream | 16-bit 输入样本,TDATA 取 [15:0] |
out_stream | Master AXI Stream | 16-bit 滤波输出 |
s_axi_control | Slave AXI-Lite | ap_start/ap_done 控制,地址空间 4KB |
ap_clk | 输入时钟 | 100 MHz(接 FCLK_CLK0) |
ap_rst_n | 低有效复位 | 接 PS7 的 FCLK_RESET0_N |
关于 s_axi_control 的注意事项:HLS 生成的 AXI-Lite 控制接口里,偏移 0x00 的 ap_ctrl 寄存器需要写 0x01(ap_start=1)才能让 IP 开始工作。对于流式处理,推荐在启动阶段写一次 ap_start,然后把 auto-restart(ap_ctrl[7])位置 1,让 IP 持续处理数据流,不需要 PS 每次手动触发。
8. 性能验证
8.1 用 ILA 抓 AXI Stream 波形
在 Vivado Block Design 里对 in_stream 和 out_stream 的 TDATA 插入 ILA:
# Tcl Console(在 Block Design 中)
create_bd_cell -type ip -vlnv xilinx.com:ip:ila:6.2 ila_0
set_property -dict [list \
CONFIG.C_NUM_OF_PROBES {4} \
CONFIG.C_DATA_DEPTH {4096} \
CONFIG.C_PROBE0_WIDTH {16} \
CONFIG.C_PROBE1_WIDTH {1} \
CONFIG.C_PROBE2_WIDTH {16} \
CONFIG.C_PROBE3_WIDTH {1} \
] [get_bd_cells ila_0]
连接:
- Probe 0 →
in_stream.TDATA[15:0] - Probe 1 →
in_stream.TVALID - Probe 2 →
out_stream.TDATA[15:0] - Probe 3 →
out_stream.TVALID
8.2 Python 脚本:从 PS 侧注入测试向量
# fir_test.py — 通过 AXI DMA 测试 FIR IP(在 PetaLinux 上运行)
# 依赖:pynq(或手写 /dev/mem 版本)
import numpy as np
import struct, os, mmap, time
# --- 用 /dev/mem 访问 FIR AXI-Lite 控制寄存器 ---
FIR_BASE = 0x43C10000 # 根据 Vivado Address Editor 实际分配
PAGE_SIZE = 4096
def fir_ctrl_write(fd_mem, offset, value):
m = mmap.mmap(fd_mem, PAGE_SIZE, mmap.MAP_SHARED,
mmap.PROT_READ | mmap.PROT_WRITE, offset=FIR_BASE)
m.seek(offset)
m.write(struct.pack('<I', value))
m.close()
fd = os.open('/dev/mem', os.O_RDWR | os.O_SYNC)
# 启动 FIR,设置 auto-restart(ap_ctrl[7]=1, ap_start[0]=1 → 0x81)
fir_ctrl_write(fd, 0x00, 0x81)
print("FIR IP 已启动,auto-restart 模式")
# --- 注入 100 Hz 测试音 ---
FS = 48000
N = 1024
t = np.arange(N) / FS
# 100 Hz(通带)+ 20kHz(阻带)合成信号
sig = 0.4 * np.sin(2 * np.pi * 100 * t) + 0.4 * np.sin(2 * np.pi * 20000 * t)
sig_q13 = np.clip(sig * 8192, -8192, 8191).astype(np.int16)
print(f"输入信号: {N} 样本, 100Hz + 20kHz 混合")
print(f"输入 RMS = {np.sqrt(np.mean(sig_q13.astype(float)**2)):.1f} LSB")
# 后续接 AXI DMA 注入(见第 12 篇),此处只演示控制逻辑
os.close(fd)
8.3 实测数字
在 Pynq-Z2 上,100 MHz 时钟下的实测结果:
| 指标 | 数值 |
|---|---|
| 最高时钟频率(Fmax) | 108 MHz(WNS = +0.7 ns) |
| 吞吐量(II=1 @ 100MHz) | 100 MSPS |
| 延迟(7 个时钟 @ 100MHz) | 70 ns |
| 20 kHz 阻带衰减(实测) | -22 dB(理论 -23 dB) |
| 100 Hz 通带增益 | 0.0 dB ± 0.2 dB |
| 功耗增量(仅 FIR IP) | <15 mW |
9. HLS 的真实局限:哪些坑会让你后悔用 HLS
9.1 AXI-Lite 控制接口的局限
HLS 自动生成的 s_axi_control 接口有几个问题:
问题 1:寄存器地址不可预测
HLS 综合器会根据 C 函数的参数顺序自动分配 AXI-Lite 地址。如果你改了 C 代码里的参数顺序,地址会变——但 PS 侧的驱动代码里是硬编码的地址,结果静默出错。
解决:在每次综合后,查阅 solution1/syn/report/fir_filter_csynth.rpt 里的 “Latency” 和 “Interface” 部分,里面会列出:
* AP_CTRL
+-----------+----------+----------+
| Interface | Register | Offset |
+-----------+----------+----------+
| AXILiteS | CTRL | 0x00 |
| AXILiteS | GIER | 0x04 |
| AXILiteS | IP_IER | 0x08 |
| AXILiteS | IP_ISR | 0x0c |
+-----------+----------+----------+
问题 2:ap_start 必须每次触发
默认的 ap_ctrl_hs 模式下,每次处理完一批数据,ap_done 置 1 后 IP 会停下来等下一次 ap_start。对于连续流式处理,需要:
- 要么用
ap_ctrl_chain(IP Customization 里选) - 要么在 HLS 代码里加
#pragma HLS INTERFACE ap_ctrl_none port=return(去掉控制接口,IP 一直运行)
对于 AXI Stream 流水线 IP,推荐直接用 ap_ctrl_none——Stream 接口的反压机制(TREADY)已经天然提供流量控制,不需要 ap_start 额外控制。
9.2 调试痛点
| 问题 | 现象 | 解法 |
|---|---|---|
| RTL 行为和 C 仿真不一致 | C sim PASS,co-sim FAIL | 用 Schedule Viewer 找时序异常;检查 volatile/static 语义 |
| 综合时间过长 | 10 万行 C 代码综合 30 分钟 | 拆分成小函数分别综合;避免在 HLS 里写巨型 struct |
| 资源超出预期 | LUT 比手写 RTL 多 3 倍 | 检查是否有没展开的循环(missing UNROLL);检查 ap_fixed 位宽是否过宽 |
| Fmax 不达标 | 时序报告 WNS < 0 | 加 PIPELINE II=2 放松要求;拆 critical path;提高目标时钟周期 |
🚧 避坑:HLS 综合里有一个常见误区——以为加了
#pragma HLS PIPELINE就一定能得到 II=1。实际上,如果循环体内有数据依赖(比如本次循环的输出是下次循环的输入,而且延迟 > 1),HLS 会把 II 自动增大到满足依赖的最小值,并在报告里给出警告。一定要在Synthesis Summary里确认 achieved II 等于 target II,而不是只看 target。
10. 本篇你应该带走的判断
- 能独立创建 Vitis HLS 工程,选对 Part(
xc7z020clg400-1)和时钟周期(10 ns) - 理解
ap_fixed<16,2>的位宽含义,能把浮点系数转成定点 - 知道
#pragma HLS PIPELINE II=1+#pragma HLS ARRAY_PARTITION complete+#pragma HLS INTERFACE axis这三个 pragma 联合起来才能得到 II=1 的 AXI Stream FIR - 能从综合报告里读出 Achieved II、Latency、LUT/FF/DSP 数字
- 知道 C/RTL 联仿里 stream timeout 的常见原因(testbench 调用次数 ≠ 样本数)
- 知道 HLS 生成的 AXI-Lite 控制接口的自动重启问题,以及
ap_ctrl_none的使用场景
11. 下一篇预告
下一篇 《Zynq 实战 16|PYNQ 框架:Jupyter 里 Python 控 PL》,我们会:
- 把本篇生成的 FIR IP 打包成 PYNQ Overlay(
.bit+.hwh) - 在 Jupyter Notebook 里用 3 行 Python 加载 Overlay 并控制 IP
- 用
pynq.lib.dma做 AXI DMA 数据搬运(接第 12 篇) - 分析 PYNQ 和原生 PetaLinux 的取舍
参考资料
| 文档号 | 名称 | 用途 |
|---|---|---|
| UG902 | Vivado Design Suite User Guide: High-Level Synthesis 2023.2 | pragma 完整参考,综合报告字段说明 |
| UG1399 | Vitis HLS User Guide 2023.2 | Vitis HLS GUI 操作、C/RTL Co-Simulation 设置 |
| UG998 | Introduction to FPGA Design Using High-Level Synthesis | HLS 入门原理,适合 II/Latency 概念建立 |
| XAPP795 | Floating-Point Design with Vivado HLS | 浮点 vs 定点 HLS 对比;ap_fixed 精度分析方法 |
| PG198 | AXI4-Stream Infrastructure IP Suite Product Guide | AXI Stream 协议细节,TVALID/TREADY 握手时序 |
| scipy.signal.firwin | SciPy 文档 | Python 生成 FIR 系数工具(firwin 函数) |
所有 AMD 文档可在 docs.amd.com 免费下载。
这是《Zynq FPGA 嵌入式系统设计实战》系列第 15 篇。 如果你在 HLS pragma 调优、联仿失败、或资源超标上遇到了具体问题,欢迎留言——最好附上综合报告里的 Achieved II 和 Slack 数字。