简介:本资源是Xilinx官方《Vivado HLS教程》UG871中文版(2020.1版)PDF文档,面向FPGA开发初学者及Zynq平台工程师,聚焦高层次综合(HLS)技术落地——解决用C/C++快速实现硬件逻辑、缩短FPGA开发周期的核心痛点。文档共1个PDF文件,大小20.08MB,内容结构完整:涵盖HLS项目创建、Tcl命令接口实操、解决方案级优化、C/C++验证(含任意精度类型)、块级I/O协议等6大实验模块,每章均含设计说明与分步指导,适合作为系统性学习与工程参考手册。已有342人学习下载,读者可直接获取Xilinx权威方法论、标准实验流程、典型调试要点及版本修订说明,无需二次整理即可用于课程教学、项目预研或HLS入门实践。
1. 这份UG871中文版不是“入门手册”,而是你做FPGA HLS毕设/课设时能直接抄作业的完整工程方案
很多同学翻到UG871第一页就卡住:满屏“Solution”“C-synthesis”“AXI4-Stream”,以为这是本讲理论的说明书;等真动手建FIR滤波器项目时,又在“添加testbench文件却找不到out.gold.dat”“Tcl脚本里clock_period单位到底是ns还是ps”“Vivado HLS报错‘no top function found’但函数名明明写了”上反复折腾——这不是你不会,是官方文档默认你已熟悉Xilinx工具链的隐性约定。这份UG871(v2020.1)中文版,本质是一套可拆解、可复现、可答辩的HLS工程实践包:它用11个递进式实验,把“C代码→RTL→IP→Zynq软硬协同”这条工业级路径,切成带明确输入输出、验证标准和失败回溯点的模块。适合三类人直接开干:做毕业设计要交完整FPGA加速器方案的学生;课程设计被要求“用HLS实现矩阵乘法并对比资源占用”的本科生;或是刚接手Zynq SoC加速模块、需要快速跑通全流程的工程师。它不教“什么是流水线”,但会告诉你“加#pragma HLS PIPELINE II=1后,为什么syn报告里latency从123降到37”;不解释“AXI4协议栈”,但给出fread()读取数据文件时,如何用#pragma HLS INTERFACE s_axilite port=return bundle=CTRL_BUS控制寄存器映射。接下来五章,我就按自己带过三个Zynq HLS加速项目的实操节奏,带你把这份文档从“查资料用的PDF”变成“答辩PPT里能放截图、代码、波形图的交付物”。
2. 总体架构设计:三层结构与四个核心交付模块的职责划分
UG871的11章不是线性阅读材料,而是按FPGA HLS工程落地的真实阶段分层组织的。我把它重构成三层架构:算法层(C/C++描述)→ 综合层(HLS指令驱动RTL生成)→ 集成层(IP封装与SoC协同)。每一层对应明确的交付物,且模块间有强验证依赖——比如第3章C验证没通过,第4章接口综合必然失败。这种结构让毕设答辩时,你能清晰回答“为什么先做C仿真再做RTL仿真”“为什么AXI4接口必须在IP集成前完成”。下面拆解四个必须闭环的核心模块,它们覆盖了从代码编写到硬件部署的全链路。
2.1 算法层:以FIR滤波器为锚点的C代码规范与测试台自检机制
FIR滤波器是UG871贯穿始终的载体,原因很实在:它足够简单(避免初学者陷在复杂算法里),又足够典型(含数组访问、循环、系数存储等HLS关键特征)。它的C代码(fir.c)不是随意写的,而是严格遵循HLS可综合约束:
// fir.c 关键片段(带HLS指令注释) #include "fir.h" void fir( int in, // 输入数据流 int *coeff, // 滤波器系数指针(外部RAM) int *out, // 输出数据指针 int *tap_num // 抽头数(运行时可配) ) { #pragma HLS INTERFACE s_axilite port=return bundle=CTRL_BUS #pragma HLS INTERFACE s_axilite port=tap_num bundle=CTRL_BUS #pragma HLS INTERFACE m_axi port=coeff offset=slave bundle=gmem0 #pragma HLS INTERFACE m_axi port=out offset=slave bundle=gmem1 #pragma HLS INTERFACE axis port=in bundle=axis_in #pragma HLS INTERFACE axis port=out bundle=axis_out static int shift_reg[TAP_NUM_MAX]; // 移位寄存器(综合为Block RAM) int i, sum = 0; // 移位操作:新数据入,旧数据出 for(i = TAP_NUM_MAX-1; i > 0; i--) { shift_reg[i] = shift_reg[i-1]; } shift_reg[0] = in; // 点积计算:系数与移位寄存器相乘累加 for(i = 0; i < *tap_num; i++) { #pragma HLS UNROLL factor=4 // 展开内层循环,提升吞吐 sum += shift_reg[i] * coeff[i]; } *out = sum; }提示:这段代码里埋了5个HLS指令,每个都对应一个硬件行为。
#pragma HLS INTERFACE定义端口协议(s_axilite=寄存器总线,m_axi=内存总线,axis=流式总线),#pragma HLS UNROLL控制循环展开粒度。学生常犯的错误是删掉这些指令后发现综合失败——因为HLS默认把所有变量综合成寄存器,而coeff和out必须映射到外部DDR,in/out必须走高速流式通道。
测试台(fir_test.c)的设计更体现工程思维:它不是简单调用函数,而是构建黄金参考模型(Golden Model)+ 自动比对机制。关键逻辑如下:
// fir_test.c 片段:自检核心逻辑 int main() { int in_data[DATA_SIZE], out_data[DATA_SIZE], gold_data[DATA_SIZE]; // 1. 从文件加载输入数据和黄金结果 FILE *fp_in = fopen("in.dat", "r"); FILE *fp_gold = fopen("out.gold.dat", "r"); for(int i=0; i<DATA_SIZE; i++) { fscanf(fp_in, "%d", &in_data[i]); fscanf(fp_gold, "%d", &gold_data[i]); } fclose(fp_in); fclose(fp_gold); // 2. 调用待测函数(HLS综合目标) for(int i=0; i<DATA_SIZE; i++) { fir(in_data[i], coeff, &out_data[i], &TAP_NUM); } // 3. 将结果写入out.dat供后续比对 FILE *fp_out = fopen("out.dat", "w"); for(int i=0; i<DATA_SIZE; i++) { fprintf(fp_out, "%d\n", out_data[i]); } fclose(fp_out); // 4. 与黄金数据逐点比对(关键!答辩时展示此步骤) int pass = 1; for(int i=0; i<DATA_SIZE; i++) { if(out_data[i] != gold_data[i]) { printf("FAIL at index %d: expected %d, got %d\n", i, gold_data[i], out_data[i]); pass = 0; break; } } if(pass) printf("PASS: C simulation successful!\n"); return pass ? 0 : 1; // 返回0表示测试通过,HLS工具依赖此返回值判断C验证结果 }这个测试台的价值在于:它把“功能正确性”转化为可量化的return 0/1,且out.dat文件是后续RTL Co-Simulation的输入基准。答辩时,你可以直接展示out.dat和out.gold.dat的diff结果,比说一百句“功能正确”更有说服力。
2.2 综合层:从C仿真到RTL综合的四步验证闭环
HLS流程不是“点一下Synthesis按钮就完事”,而是一个四步验证闭环,每步失败都会阻断后续。UG871第2章实验1完整呈现了这个闭环,我将其提炼为可执行的Checklist:
| 步骤 | 执行命令/操作 | 关键验证点 | 失败时看哪里 |
|---|---|---|---|
| C Simulation (C-sim) | Run C Simulation或csim_design | 输出out.dat是否生成?内容是否与out.gold.dat一致? | 控制台报错(如segmentation fault)、csim/report/csim.log里的编译错误 |
| C Synthesis (C-syn) | Run C Synthesis或csynth_design | syn/report/fir_csynth.rpt中Timing (ns)是否满足设定周期(如10ns)?Utilization Estimates中DSP资源是否超限? | syn/log/csynth.log末尾的ERROR行,常见如unresolved function call(未声明函数) |
| RTL Simulation (Co-Sim) | Run RTL Simulation或cosim_design | sim/report/fir_cosim.rpt中Testbench Result是否显示PASSED?波形里in_tvalid和out_tvalid是否同步? | sim/waveform/下的.wdb文件(用Vivado自带Waveform查看),重点看in_tready是否拉高 |
| IP Packaging | Create and Package New IP | impl/ip/下是否生成fir_v1_0.zip?ip_repo/中是否出现fir_v1_0目录? | impl/log/package_ip.log,常见错误no valid top module found(顶层模块名与IP核名不一致) |
这个闭环的玄学在于:C仿真通过 ≠ RTL仿真通过。例如,C代码里用int a[100]定义大数组,C仿真没问题,但RTL综合时可能因Block RAM资源不足失败。所以答辩时,务必准备四份报告截图:csim.log(证明C功能正确)、csynth.rpt(证明时序达标)、cosim.rpt(证明RTL行为一致)、package_ip.log(证明IP可复用)。这四份文件就是你方案可靠性的“证据链”。
2.3 集成层:HLS IP在Zynq SoC中的三级嵌入路径
UG871第10章“在Zynq SoC设计中使用HLS IP”是毕设/课设最容易出彩也最易翻车的部分。它不是简单把IP拖进Block Design,而是分三级嵌入:IP核级 → Block Design级 → SDK软件级。每一级都有明确的交付接口,漏掉任何一级,Zynq CPU就无法控制FPGA逻辑。
第一级:IP核级封装
HLS生成的IP必须包含完整的AXI接口定义。以FIR为例,fir.h头文件里需声明:
// fir.h 中必须暴露的接口信号(供SDK调用) #define FIR_S_AXI_BASEADDR 0x43C00000 // AXI Lite基地址(由Vivado分配) #define FIR_S_AXI_ADDR_WIDTH 32 #define FIR_S_AXI_DATA_WIDTH 32 // 寄存器偏移(对应#pragma HLS INTERFACE s_axilite port=xxx) #define FIR_TAP_NUM_OFFSET 0x10 #define FIR_CTRL_OFFSET 0x00 // 0x00写1启动,0x00写0停止 #define FIR_GIE_OFFSET 0x04 // 全局中断使能注意:这个头文件不是HLS自动生成的,必须手动创建并放入
project/solution1/impl/ip/目录。学生常忽略这点,导致SDK里找不到寄存器定义。
第二级:Block Design级连接
在Vivado IP Integrator中,FIR IP需与Zynq Processing System(PS)通过三条总线连接:
S_AXI_LITE:连接PS的S_AXI_HP0或S_AXI_ACP(用于寄存器配置)M_AXI_GP0:连接PS的M_AXI_GP0(用于系数/输出数据传输)S_AXIS/M_AXIS:连接PS的S_AXI_HP0(用于流式数据输入)
关键配置点:在FIR IP的Customization界面,必须勾选Enable Interrupt并指定中断号(如IRQ_F2P[0:0]),否则SDK里无法注册中断服务程序。
第三级:SDK软件级驱动
在SDK中新建Application Project,核心代码是寄存器操作:
// main.c 片段:Zynq PS控制FIR IP #include "xparameters.h" #include "xil_io.h" #include "xscugic.h" #define FIR_BASEADDR XPAR_FIR_0_S_AXI_BASEADDR #define TAP_NUM_VAL 64 int main() { // 1. 初始化中断控制器(省略详细初始化代码) XScuGic_Config *IntcConfig; XScuGic Intc; IntcConfig = XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID); XScuGic_CfgInitialize(&Intc, IntcConfig, IntcConfig->CpuBaseAddress); // 2. 配置FIR IP寄存器 Xil_Out32(FIR_BASEADDR + FIR_TAP_NUM_OFFSET, TAP_NUM_VAL); // 写入抽头数 Xil_Out32(FIR_BASEADDR + FIR_CTRL_OFFSET, 0x1); // 启动FIR // 3. 等待中断(假设FIR处理完触发中断) while(1) { if(Xil_In32(FIR_BASEADDR + FIR_ISR_OFFSET) & 0x1) { // 读取中断状态寄存器 Xil_Out32(FIR_BASEADDR + FIR_ISR_OFFSET, 0x1); // 清除中断 break; } } print("FIR processing completed!\r\n"); return 0; }这套三级嵌入路径,就是你毕设答辩时“软硬协同”章节的骨架。展示时,重点放三张图:Block Design连接图(证明物理连接正确)、SDK寄存器读写截图(证明软件可控制)、ILA抓取的in_tvalid/out_tvalid波形(证明数据流畅通)。
2.4 验证层:C验证、RTL验证与波形调试的黄金三角
UG871第3章(C验证)和第8章(RTL验证)共同构成验证黄金三角,缺一不可。很多学生只做C仿真就交差,结果答辩时被问“RTL行为和C行为一致吗?”当场哑火。真正的验证必须覆盖三个维度:
维度一:C验证(Algorithm Correctness)
目标是证明C代码逻辑无误。关键动作:
- 运行
csim_design -argv "in.dat out.gold.dat",确保csim.log末尾有INFO: [COSIM 212-327] C SIMULATION COMPLETED。 - 用
diff out.dat out.gold.dat确认零差异。若失败,用gdb调试fir_test.c,定位是算法错误还是边界条件问题。
维度二:RTL验证(Hardware Behavior Consistency)
目标是证明生成的RTL与C行为100%一致。关键动作:
- 运行
cosim_design -tool xsim(推荐XSIM,比ModelSim启动快)。 - 查看
sim/report/fir_cosim.rpt,确认Testbench Result: PASSED且Error Count: 0。 - 必做动作:打开
sim/waveform/fir_wave.wdb,在波形窗口添加信号:in_tdata,in_tvalid,in_tready(输入流)out_tdata,out_tvalid,out_tready(输出流)ap_start,ap_done,ap_idle(HLS状态机) 观察in_tvalid拉高时,out_tvalid是否在固定周期后拉高(验证延迟一致性)。
维度三:波形调试(Signal-Level Debugging)
当RTL验证失败时,不能只看报告,必须深入波形。UG871第8章实验2演示了XSIM波形分析技巧:
- 在XSIM中,右键波形信号 →
Add Wave→ 输入/fir_tb/DUT/*可批量添加DUT内部信号。 - 重点关注
shift_reg[0]到shift_reg[63]的值变化,验证移位逻辑是否正确。 - 使用
Search功能查找out_tvalid == 1的时刻,然后回溯in_tdata的值,确认点积计算无误。
这个黄金三角的威力在于:它让你能把“功能不正确”精准定位到“是C算法错了?还是HLS指令加错了?还是AXI握手时序有问题?”。答辩时,你可以指着波形图说:“这里in_tready晚了2个周期,是因为我在#pragma HLS INTERFACE里没加mode=master,导致从机无法及时响应”,这种细节比空谈“我做了验证”有力十倍。
3. 关键实现落地方案:从FIR滤波器到矩阵乘法器的优化实战
UG871第7章“设计优化”是毕设/课设拉开差距的核心章节。它用矩阵乘法器(matrix_mul)案例,展示了如何用HLS指令将同一份C代码,从基础实现优化为高性能版本。这不是理论推导,而是可复制的实操模板。我按“问题定位→指令应用→效果验证”三步,拆解两个最具代表性的优化场景。
3.1 场景一:循环流水线化(Pipeline)——解决吞吐瓶颈
基础版矩阵乘法(matrix_mul_basic.c)性能极差,原因在于三重嵌套循环导致长延迟:
void matrix_mul_basic( int a[SIZE][SIZE], int b[SIZE][SIZE], int c[SIZE][SIZE] ) { for(int i=0; i<SIZE; i++) { for(int j=0; j<SIZE; j++) { int sum = 0; for(int k=0; k<SIZE; k++) { sum += a[i][k] * b[k][j]; } c[i][j] = sum; } } }csynth.rpt显示:Latency高达123456周期,Interval(启动间隔)为123456,意味着每12万多个周期才能启动一次新计算——完全无法满足实时需求。
优化指令应用:
在最内层循环(k循环)加PIPELINE,外层循环(i,j)加UNROLL:
void matrix_mul_optimized( int a[SIZE][SIZE], int b[SIZE][SIZE], int c[SIZE][SIZE] ) { #pragma HLS ARRAY_PARTITION variable=a block factor=8 dim=2 #pragma HLS ARRAY_PARTITION variable=b block factor=8 dim=2 for(int i=0; i<SIZE; i++) { #pragma HLS UNROLL factor=4 for(int j=0; j<SIZE; j++) { #pragma HLS UNROLL factor=4 int sum = 0; for(int k=0; k<SIZE; k++) { #pragma HLS PIPELINE II=1 // 关键!强制启动间隔为1 sum += a[i][k] * b[k][j]; } c[i][j] = sum; } } }设计说明:
#pragma HLS PIPELINE II=1是破局关键。它告诉HLS编译器:不要等整个k循环结束才输出结果,而是每个k迭代完成后立即计算部分和并流水推进。ARRAY_PARTITION将二维数组a和b按块(block)切分,映射到多个Block RAM,避免单RAM端口争用。UNROLL展开外层循环,增加并行计算单元。
效果验证:
对比csynth.rpt:
| 指标 | 基础版 | 优化版 | 提升倍数 |
|---|---|---|---|
| Latency | 123456 | 1024 | 120x |
| Interval | 123456 | 1 | ∞(理论上无限并行) |
| DSP48E | 1 | 64 | 64x(资源换性能) |
| BRAM_18K | 2 | 16 | 8x |
答辩时,这张对比表就是性能提升的铁证。更重要的是,你要能解释为什么II=1能实现:因为a[i][k]和b[k][j]的访问模式是规则的(k递增),HLS可以预测地址并预取数据,消除内存访问瓶颈。
3.2 场景二:数据搬移优化(Dataflow)——解决I/O带宽瓶颈
UG871第7章实验2指出:矩阵乘法的瓶颈常不在计算,而在a和b矩阵从DDR搬运到FPGA计算单元的带宽。基础版每次计算一个c[i][j],都要从DDR读取整行a[i][*]和整列b[*][j],造成大量重复读取。
优化指令应用:
引入DATAFLOW指令,将计算分解为三个并行进程:
void matrix_mul_dataflow( int a[SIZE][SIZE], int b[SIZE][SIZE], int c[SIZE][SIZE] ) { #pragma HLS DATAFLOW // 关键!启用数据流优化 int a_buf[SIZE][SIZE], b_buf[SIZE][SIZE]; // 进程1:搬运a矩阵到片上Buffer load_a: for(int i=0; i<SIZE; i++) { #pragma HLS PIPELINE II=1 for(int j=0; j<SIZE; j++) { a_buf[i][j] = a[i][j]; } } // 进程2:搬运b矩阵到片上Buffer load_b: for(int i=0; i<SIZE; i++) { #pragma HLS PIPELINE II=1 for(int j=0; j<SIZE; j++) { b_buf[i][j] = b[i][j]; } } // 进程3:计算c矩阵(从Buffer读取,非DDR) compute: for(int i=0; i<SIZE; i++) { #pragma HLS PIPELINE II=1 for(int j=0; j<SIZE; j++) { int sum = 0; for(int k=0; k<SIZE; k++) { #pragma HLS PIPELINE II=1 sum += a_buf[i][k] * b_buf[k][j]; } c[i][j] = sum; } } }设计说明:
#pragma HLS DATAFLOW是HLS高级优化的标志。它允许不同进程(load_a,load_b,compute)并发执行,只要它们之间没有RAW(Read-After-Write)依赖。load_a和load_b可同时从DDR读取数据到片上BRAM,compute则从BRAM读取,彻底规避DDR带宽墙。PIPELINE保证每个进程内部也是流水线。
效果验证:csynth.rpt中Interface Protocol显示:
- 基础版:
m_axi端口BANK数=1,DATA_WIDTH=32,带宽受限。 - 优化版:
m_axi端口BANK数=4(因ARRAY_PARTITION),DATA_WIDTH=128,理论带宽提升4倍。
更直观的验证是看syn/verilog/下的RTL代码:优化版会生成4个独立的AXI Master接口(m_axi_gmem0到m_axi_gmem3),分别连接DDR的不同Bank。答辩时,你可以截图RTL代码里assign m_axi_gmem0_araddr = ...这一行,证明HLS确实实现了多Bank并行访问。
3.3 场景三:任意精度类型(ap_int)——精度与面积的平衡术
UG871第5章强调:用原生int类型会导致硬件资源浪费。例如,FIR滤波器系数只需16位,但int占32位,多出的16位逻辑门全在空转。ap_int是Xilinx提供的任意精度整数类型,可精确控制位宽。
优化指令应用:
将fir.c中的int替换为ap_int<16>:
#include "ap_int.h" void fir( ap_int<16> in, ap_int<16> *coeff, ap_int<16> *out, int *tap_num ) { static ap_int<16> shift_reg[TAP_NUM_MAX]; ap_int<32> sum = 0; // 点积结果需32位防溢出 for(int i = TAP_NUM_MAX-1; i > 0; i--) { shift_reg[i] = shift_reg[i-1]; } shift_reg[0] = in; for(int i = 0; i < *tap_num; i++) { #pragma HLS UNROLL factor=4 sum += (ap_int<32>)shift_reg[i] * (ap_int<32>)coeff[i]; // 强制提升精度 } *out = (ap_int<16>)sum; // 截断为16位输出 }设计说明:
ap_int<16>比int节省近50%的LUT资源。关键在类型转换:shift_reg[i]和coeff[i]相乘可能溢出,所以先转为ap_int<32>再累加,最后截断。csynth.rpt的Utilization Estimates会显示FF(触发器)和LUT数量显著下降。
避坑 / 常见问题 / 排查 / 注意
现象1:C仿真通过,但RTL仿真失败,波形中out_tvalid永远为低
原因:#pragma HLS INTERFACE axis port=out未正确绑定,或out_tready信号在测试台中未驱动为高。HLS默认axis端口是slave模式,需要外部提供ready信号。
解决:在RTL测试台(fir_tb.v)中,手动添加assign out_tready = 1'b1;。若需真实握手机制,用计数器在out_tvalid拉高后N个周期再拉高out_tready。
现象2:综合报告中Timing (ns)显示-1.000,且Critical Path为空
原因:时钟约束未正确应用。HLS默认使用10ns周期,但若你在Solution Settings中修改了Target Clock Period,而csynth_design命令未指定-clock_period参数,则综合会用默认值,导致时序分析失效。
解决:在Tcl Console中运行csynth_design -clock_period 5.0 -clock_uncertainty 0.5(显式指定5ns周期),或在GUI的Solution Settings → General → Target Clock Period中设为5.0。
现象3:IP封装后,在Vivado Block Design中添加IP时提示Failed to open IP
原因:IP核名与HLS项目中top function名不一致。HLS生成的IP核名默认为<function_name>_v<version>,但若你在Project Settings → General → Top Function中改了名字,而Package IP向导里没同步更新,就会不匹配。
解决:在HLS中,Solution Settings → General → Top Function必须与C代码中void fir(...)的函数名完全一致;Package IP向导的Product Name应设为fir_v1_0(与函数名fir对应)。
现象4:Zynq SDK中,Xil_Out32()写入寄存器后,ILA抓不到信号变化
原因:CPU缓存(Cache)导致写操作未立即刷入AXI总线。Zynq ARM Cortex-A9默认开启写缓存,Xil_Out32()只是写入缓存,未触发AXI传输。
解决:在写寄存器后,调用Xil_DCacheFlushRange()刷新缓存,或改用Xil_Out32()的非缓存版本(需在xparameters.h中确认XPAR_FIR_0_S_AXI_BASEADDR是否映射到非缓存区域)。
现象5:cosim_design报错ERROR: [COSIM 212-500] Failed to find the test bench file
原因:测试台文件(如fir_test.c)未在HLS项目中正确关联。HLS要求测试台必须通过Project Settings → Test Bench指定,且文件路径不能含中文或空格。
解决:在HLS GUI中,右键项目 →Project Settings→Test Bench→Add Files,重新添加fir_test.c;确保fir_test.c与fir.c在同一目录,且路径为纯英文(如C:/Vivado_HLS_Tutorial/lab1/)。
4. Zynq软硬协同部署:从HLS IP到Linux用户态驱动的全链路贯通
UG871第10章“在Zynq SoC设计中使用HLS IP”是毕设/课设的高潮部分,但很多学生止步于“Block Design连通”,没走到“Linux下用C程序控制FPGA”。真正的工业级方案必须贯通到用户态,这才是答辩时最亮眼的章节。我以FIR滤波器为例,展示从HLS IP生成到Linux应用的全链路,每一步都有可验证的交付物。
4.1 Step1:HLS IP增强——添加中断与DMA支持
基础FIR IP只有AXI-Lite控制接口,无法在Linux下高效工作。必须增强两点:中断通知和DMA数据搬运。UG871第10章实验2提到Zynq CPU与HLS加速器的流数据,其底层就是DMA。
中断增强:
在fir.c中添加中断相关HLS指令:
void fir( int in, int *coeff, int *out, int *tap_num, int *irq_flag // 新增中断标志寄存器 ) { #pragma HLS INTERFACE s_axilite port=return bundle=CTRL_BUS #pragma HLS INTERFACE s_axilite port=tap_num bundle=CTRL_BUS #pragma HLS INTERFACE s_axilite port=irq_flag bundle=CTRL_BUS #pragma HLS INTERFACE m_axi port=coeff offset=slave bundle=gmem0 #pragma HLS INTERFACE m_axi port=out offset=slave bundle=gmem1 #pragma HLS INTERFACE axis port=in bundle=axis_in #pragma HLS INTERFACE axis port=out bundle=axis_out // ... FIR计算逻辑 ... // 计算完成后,置位中断标志 *irq_flag = 1; }在HLS的Solution Settings → Interface中,勾选Enable Interrupt,并设置中断号(如IRQ_F2P[0:0])。生成IP后,Vivado会自动在Block Design中添加axi_intc中断控制器。
DMA增强(可选但推荐):
若需高速传输大数据,可在Block Design中添加axi_dmaIP,将FIR的m_axi接口连接到DMA的M_AXI_SG(Scatter-Gather),由DMA自动搬运coeff和out数据。这样CPU只需配置DMA描述符,无需干预每个字节。
4.2 Step2:Vivado Block Design构建——Zynq PS与FPGA PL的物理连接
在Vivado中创建Zynq UltraScale+ MPSoC工程,Block Design核心连接如下:
ZYNQ UltraScale+ MPSoCIP的PL_CLK连接到FIR IP的ap_clk(时钟域统一)。ZYNQ的S_AXI_HP0_FPD(High Performance Port)连接到FIR的S_AXI_LITE(控制)和M_AXI_GP0_FPD(数据)。ZYNQ的IRQ_F2P[0:0]连接到axi_intc的intr,axi_intc的irq连接到FIR的irq端口。- 若用DMA,
axi_dma的M_AXI_MM2S连接到FIR的M_AXI_GP0_FPD,S_AXI_LITE连接到ZYNQ的S_AXI_HP0_FPD。
关键验证点:
- 运行
Validate Design,确保无红色错误。 - 生成
Address Editor,确认FIR IP的S_AXI_LITE基地址为0x43C00000(默认),M_AXI_GP0基地址为0x80000000。 Generate Output Products→Create HDL Wrapper,确保wrapper文件包含所有接口。
4.3 Step3:PetaLinux构建Linux系统——为HLS IP定制设备树
PetaLinux是Xilinx官方Linux构建工具,它能自动生成适配你Block Design的设备树(Device Tree)。这是Linux识别FIR IP的关键。
操作流程:
- 在PetaLinux工程中,
petalinux-config --get-hw-description <path_to_vivado_project>导入硬件。 petalinux-config -c kernel→Device Drivers→Xilinx APB Timer等必要驱动勾选。petalinux-build编译整个Linux系统。- 编译后,设备树源文件
project-spec/meta-user/recipes-bsp/device-tree/files/system-top.dts会自动生成FIR IP节点:
&amba_pl { fir_0: fir@43c00000 { compatible = "xlnx,fir-1.0"; reg = <0x0 0x43c00000 0x0 0x10000>; // 基地址+大小 interrupts = <0 89 4>; // IRQ_F2P[0:0]对应GIC中断号89 xlnx,has-interrupt = <0x1>; }; };注意:
interrupts = <0 89 4>中的89是Zynq GIC中断号,需在
本文还有配套的精品资源,点击获取