保姆级教程十:软硬大闭环!ZYNQ Linux下联合调用HLS与DMA实现硬件加速(全网最通透)

保姆级教程十:软硬大闭环!ZYNQ Linux下联合调用HLS与DMA实现硬件加速(全网最通透) 保姆级教程十软硬大闭环ZYNQ Linux下联合调用HLS与DMA实现硬件加速全网最通透文章目录保姆级教程十软硬大闭环ZYNQ Linux下联合调用HLS与DMA实现硬件加速全网最通透️ 第一步把 HLS 硬件导入 Vivado (搭积木)1. 导入自定义 IP 库2. 改造 Block Design (插入乘法器)3. 查看物理地址极其关键 第二步PetaLinux 刷新系统配置 第三步揭秘 HLS 的启动钥匙控制寄存器 第四步C语言实战召唤神龙的时刻编译与运行❓ 终极排雷指南 (FAQ)一路跟到第十篇的兄弟们欢呼吧今天我们将迎来 ZYNQ 嵌入式开发中最具成就感的一战真正的软硬件协同加速以前我们都是用 ARMPS端的 CPU 去算乘法、算图像速度慢得像老牛拉破车。今天我们要把计算任务丢给上一篇用 HLS 生成的“硬件乘法器”让 FPGA 瞬间秒杀这些计算并用 DMA 实现海量数据的高速搬运准备好迎接真正的黑科技了吗发车️ 第一步把 HLS 硬件导入 Vivado (搭积木)我们先打开第7篇教程中做好的那个“DMA 环回测试”的 Vivado 工程。1. 导入自定义 IP 库在 Vivado 左侧导航栏点击Settings。展开IP- 点击Repository。点击号找到你上一篇 Vitis HLS 工程里的solution1/impl/ip文件夹点击 Select。此时 Vivado 会弹出一个窗口告诉你识别到了 1 个 IP这就是我们的Hw_multiplier点击 OK。2. 改造 Block Design (插入乘法器)打开你的 Block Design 图纸。找到 DMA 发货口M_AXIS_MM2S和收货口S_AXIS_S2MM之间连着的那根线选中它按 Delete 键删掉切断原来的直接环回。在空白处右键 -Add IP- 搜索hw_multiplier把它添加进来。核心连线把 DMA 的M_AXIS_MM2S连到乘法器的输入流in_stream。把 乘法器的输出流out_stream连到 DMA 的S_AXIS_S2MM。控制线连线乘法器上面有一个s_axi_CTRL_BUS这是它的控制寄存器。点击上方的Run Connection AutomationVivado 会自动帮把它连到 PS 端的主干道上。3. 查看物理地址极其关键点击顶部的Address Editor标签页。你会看到两个重要的外设地址axi_dma_0比如是0x40400000hw_multiplier_0比如是0x40000000把这个地址记在小本本上一会写 C 语言要用老规矩Generate Bitstream - Export Hardware 导出包含 bitstream 的.xsa文件。 第二步PetaLinux 刷新系统配置把新的.xsa图纸丢进 Ubuntu 虚拟机里的 PetaLinux 工程。执行更新命令petalinux-config --get-hw-description/你的xsa路径/(保存并退出)因为我们在第7篇已经在设备树Device Tree里给 DMA 预留了 16MB 的连续物理内存0x10000000所以这里不需要再改设备树了直接编译打包petalinux-build petalinux-package--boot--fsblimages/linux/zynq_fsbl.elf--fpgaimages/linux/system.bit --u-boot--force把新的BOOT.BIN和image.ub拷入 SD 卡开发板上电开机 第三步揭秘 HLS 的启动钥匙控制寄存器在写 C 语言之前必须科普一个小白最容易踩的坑HLS 生成的 IP 核通电后默认是“休眠”状态的你不去踹它一脚它绝对不干活怎么踹通过刚才记下的0x40000000这个地址。Xilinx 规定HLS 生成的 IP基地址偏移0x00的地方就是它的总控制寄存器Control RegisterBit 0 (ap_start)写 1代表启动运算。Bit 7 (auto_restart)写 1代表自动重启。小白神级操作我们只要往0x40000000写入0x81二进制的 10000001这个乘法器就会开启无限续航模式只要 DMA 送来数据它立马乘 2 吐出来 第四步C语言实战召唤神龙的时刻在 Linux 系统里新建hls_dma_test.c。这段代码将同时控制DMA 发车HLS 启动内存读写。#includestdio.h#includeunistd.h#includefcntl.h#includesys/mman.h// --- 我们在 Vivado 里查到的物理地址 ---#defineDMA_REG_BASE0x40400000// DMA 控制寄存器#defineHLS_REG_BASE0x40000000// HLS 乘法器控制寄存器#defineDMA_MEM_BASE0x10000000// 设备树里预留的物理内存火车站// --- 内存区划分 ---#defineTX_BUFFER(DMA_MEM_BASE0x0000000)// 10000000 发送区#defineRX_BUFFER(DMA_MEM_BASE0x0800000)// 10800000 接收区// --- DMA 寄存器偏移 ---#defineMM2S_CR0x00// TX 控制#defineMM2S_SA0x18// TX 源地址#defineMM2S_LENGTH0x28// TX 长度 (触发发送)#defineS2MM_CR0x30// RX 控制#defineS2MM_DA0x48// RX 目标地址#defineS2MM_LENGTH0x58// RX 长度 (触发接收)intmain(){intmem_fd;void*dma_reg,*hls_reg,*tx_ram,*rx_ram;// 1. 打开 /dev/mem (使用 O_SYNC 避免 Cache 捣乱)mem_fdopen(/dev/mem,O_RDWR|O_SYNC);if(mem_fd0){printf(打开 /dev/mem 失败\n);return-1;}// 2. 映射所有的物理地址到虚拟地址dma_regmmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,mem_fd,DMA_REG_BASE);hls_regmmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,mem_fd,HLS_REG_BASE);tx_rammmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,mem_fd,TX_BUFFER);rx_rammmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,mem_fd,RX_BUFFER);volatileunsignedint*dma(volatileunsignedint*)dma_reg;volatileunsignedint*hls(volatileunsignedint*)hls_reg;volatileunsignedint*tx_data(volatileunsignedint*)tx_ram;volatileunsignedint*rx_data(volatileunsignedint*)rx_ram;// // 3. 准备数据 (往 TX 内存放 5 个数字)// for(inti0;i5;i){tx_data[i]i1;// 放入 1, 2, 3, 4, 5rx_data[i]0;// 清空接收区}printf(发送前的数据: %d, %d, %d, %d, %d\n,tx_data[0],tx_data[1],tx_data[2],tx_data[3],tx_data[4]);// // 4. 点火启动 HLS 硬件加速器// // 往偏移 0x00 写 0x81 (Auto Restart 1, Start 1)*hls0x81;printf(HLS 硬件乘法器已启动待命...\n);// // 5. 启动 DMA 传输// // 开启 DMA 通道*(dma(MM2S_CR/4))1;*(dma(S2MM_CR/4))1;// 设置收发物理地址*(dma(MM2S_SA/4))TX_BUFFER;*(dma(S2MM_DA/4))RX_BUFFER;// 触发传输长度为 5 个整数 20 字节 (必须先开 RX 再开 TX)*(dma(S2MM_LENGTH/4))20;*(dma(MM2S_LENGTH/4))20;// 等待传输完成 (约几微秒)usleep(100000);// // 6. 验证加速结果// printf(\n--- DMA 传输完毕 ---\n);printf(接收到的数据: %d, %d, %d, %d, %d\n,rx_data[0],rx_data[1],rx_data[2],rx_data[3],rx_data[4]);// 清理现场munmap(dma_reg,4096);munmap(hls_reg,4096);munmap(tx_ram,4096);munmap(rx_ram,4096);close(mem_fd);return0;}编译与运行arm-linux-gnueabihf-gcc hls_dma_test.c-ohls_dma_testchmodx hls_dma_test ./hls_dma_test见证奇迹的时刻到了你的屏幕上将输出发送前的数据: 1, 2, 3, 4, 5 HLS 硬件乘法器已启动待命... --- DMA 传输完毕 --- 接收到的数据: 2, 4, 6, 8, 10 完美硬件乘法器不仅收到了数据而且成功把每个数字乘了 2并通过 DMA 原封不动地还给了 Linux 内存❓ 终极排雷指南 (FAQ)Q1程序跑了但是卡在usleep之后死机或者 DMA 不返回数据神级细节揭秘这是 AXI-Stream 的TLAST最后一个数据标志惹的祸DMA 接收通道S2MM必须看到TLAST信号被拉高才知道一包数据传完了。回头看第9篇我们在 HLS 的 C 代码里写了一句val_out.last val_in.last;。这句话就是魔法它把进入乘法器的TLAST标志原封不动地传了出去。如果你手写 HLS 时忘了传递TLASTDMA 就会一直死等导致总线挂死Q2输出的数据全是 0 是怎么回事嫌疑1你忘了给 HLS 写入0x81启动信号乘法器还在睡大觉。嫌疑2缓存一致性Cache问题记得确认你的open函数带上了O_SYNC标志。Q3这个乘 2我在 C 语言里一个for循环不就算完了吗干嘛费这么大劲对于 5 个数字确实 C 语言快。但如果是一张1920x1080 的高清图像200万个像素点呢在 C 语言里CPU 要循环 200 万次而在我们搭建的这套“DMA HLS 纯硬件流水线”里这 200 万个数据会像水流一样每个时钟周期几纳秒就处理完一个这才是 FPGA 硬件加速的真正威力结语兄弟们当你看到终端里打印出2, 4, 6, 8, 10的那一刻你已经走完了 ZYNQ 开发者最艰难的一段路。Vivado 建硬件 - PetaLinux 编系统 - HLS 写算法 - C 语言控寄存器 - DMA 传数据。这一整套丝滑的连招就是目前工业界医疗影像、机器视觉、雷达信号处理最主流的开发范式