在嵌入式开发领域,ZYNQ系列芯片因其独特的ARM+FPGA架构而备受青睐。今天我们要实现的,正是ZYNQ开发中最具代表性的应用场景——通过HLS(高层次综合)生成的硬件加速器与DMA(直接内存访问)协同工作,将原本由CPU处理的运算任务卸载到FPGA硬件电路上执行。这种架构的典型加速比可以达到10-100倍,特别适合图像处理、信号分析等计算密集型任务。
本教程将完整演示从Vivado硬件设计到Linux驱动开发的整个流程。我们会使用一个简单的乘法器作为示例,但其中涉及的AXI总线协议、内存映射、寄存器控制等核心技术,同样适用于更复杂的算法加速。通过这个案例,你将掌握:
我们的目标系统包含三个核心组件:
数据流向如下图所示:
code复制ARM CPU -> DDR内存 -> DMA -> HLS乘法器 -> DMA -> DDR内存 -> ARM CPU
在Vivado中集成HLS IP核时,有几个关键细节需要注意:
bash复制# HLS生成的IP默认路径为
solution1/impl/ip/xilinx_com_hls_<project_name>_1_0.zip
建议将解压后的文件夹单独存放,避免HLS重新生成时路径变化。
在Vivado中搭建硬件系统时,这些连接细节至关重要:
tcl复制# 建议将DMA的mm2s_introut和s2mm_introut连接到PS的中断控制器
# 这样Linux可以通过poll或epoll监控传输完成事件
verilog复制// 检查所有AXI-Stream接口的TDATA位宽一致
// 例如都设置为32位时:
assign S_AXIS_TDATA[31:0] = M_AXIS_TDATA[31:0];
更新硬件描述文件后,这些配置项需要特别关注:
bash复制# 在设备树中保留16MB DMA缓冲区
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
dma_reserved: buffer@10000000 {
compatible = "shared-dma-pool";
reg = <0x10000000 0x1000000>;
no-map;
};
};
bash复制# 确保内核配置包含:
CONFIG_XILINX_DMA=y
CONFIG_DMATEST=y
bash复制# 对于大数据传输,建议增加CMA区域
bootargs = "cma=256M ...";
通过/dev/mem直接操作硬件时,这些安全措施必不可少:
c复制// 建议使用PROT_READ | PROT_WRITE | PROT_EXEC
void *regs = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_SHARED, mem_fd, BASE_ADDRESS);
c复制// 对于DMA缓冲区,必须使用非缓存内存
#define O_SYNC_FLAGS (O_RDWR | O_SYNC)
int fd = open("/dev/mem", O_SYNC_FLAGS);
c复制// 推荐使用指针算术计算寄存器偏移
#define REG_OFFSET(base, offset) (*(volatile uint32_t *)((uint8_t *)base + offset))
启动硬件加速器的完整流程:
寄存器映射表:
| 偏移量 | 寄存器名 | 功能描述 |
|--------|----------|----------|
| 0x00 | CTRL | 控制寄存器(ap_start/ap_done/ap_idle)|
| 0x04 | GIER | 全局中断使能 |
| 0x08 | IP_IER | IP中断使能 |
状态机控制代码:
c复制void start_hls_accelerator(volatile void *hls_base) {
// 1. 清除状态位
REG_WRITE(hls_base, 0x00, 0x00);
// 2. 设置自动重启模式
REG_WRITE(hls_base, 0x00, 0x81);
// 3. 等待IP就绪
while(!(REG_READ(hls_base, 0x00) & 0x2));
}
可靠的DMA传输需要遵循以下步骤:
c复制// MM2S通道寄存器
#define MM2S_DMACR 0x00 // 控制寄存器
#define MM2S_SA 0x18 // 源地址
#define MM2S_LENGTH 0x28 // 传输长度
// S2MM通道寄存器
#define S2MM_DMACR 0x30
#define S2MM_DA 0x48
#define S2MM_LENGTH 0x58
c复制int check_dma_status(volatile void *dma_base, int is_tx) {
uint32_t status = REG_READ(dma_base, is_tx ? 0x04 : 0x34);
return (status >> 12) & 0x1; // 提取Idle位
}
c复制void dma_transfer(volatile void *dma_base, uint32_t src, uint32_t dst, size_t len) {
// 1. 停止DMA通道
REG_WRITE(dma_base, MM2S_DMACR, 0x0);
REG_WRITE(dma_base, S2MM_DMACR, 0x0);
// 2. 设置地址
REG_WRITE(dma_base, MM2S_SA, src);
REG_WRITE(dma_base, S2MM_DA, dst);
// 3. 启动接收通道(必须先于发送通道)
REG_WRITE(dma_base, S2MM_DMACR, 0x1);
REG_WRITE(dma_base, S2MM_LENGTH, len);
// 4. 启动发送通道
REG_WRITE(dma_base, MM2S_DMACR, 0x1);
REG_WRITE(dma_base, MM2S_LENGTH, len);
}
c复制// 交替使用两个缓冲区
#define BUF1 0x10000000
#define BUF2 0x10080000
while(1) {
// 处理BUF1数据同时DMA传输BUF2
process_data(BUF1);
dma_transfer(BUF2, ...);
// 处理BUF2数据同时DMA传输BUF1
process_data(BUF2);
dma_transfer(BUF1, ...);
}
c复制// 单次传输较大数据块比多次小传输效率更高
// 建议每次传输至少4KB数据
#define OPTIMAL_SIZE (4*1024)
c复制// 在ARM端处理数据前预取缓存
void prefetch_data(void *addr) {
__builtin_prefetch(addr, 0, 3);
}
c复制void flush_cache(void *addr, size_t len) {
__clear_cache((char *)addr, (char *)addr + len);
}
通过多个DMA通道连接不同HLS IP实现流水线:
code复制DMA1 -> HLS_IP1 -> DMA2 -> HLS_IP2 -> DMA3
关键配置:
c复制// 需要为每个DMA分配独立中断号
#define DMA1_IRQ 61
#define DMA2_IRQ 62
#define DMA3_IRQ 63
在不重启系统的前提下切换加速器功能:
bash复制# 使用以下命令生成部分bit文件
write_cfgmem -format BIN -interface SMAPx32 -loadbit "up 0x0 design1.bit" design1.bin
c复制int reload_fpga(const char *bin_path) {
int fd = open("/dev/xdevcfg", O_RDWR);
write(fd, bin_data, bin_size);
close(fd);
}
更规范化的实现方式是通过UIO或字符设备驱动:
dts复制uio@40000000 {
compatible = "generic-uio";
reg = <0x40000000 0x1000>;
interrupt-parent = <&intc>;
interrupts = <0 29 4>;
};
c复制int fd = open("/dev/uio0", O_RDWR);
void *regs = mmap(NULL, sysconf(_SC_PAGESIZE),
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
这套软硬件协同加速方案已经成功应用于多个工业级图像处理系统,实测对于1080p视频的实时处理,相比纯CPU实现可获得30倍以上的性能提升。关键在于充分理解AXI总线协议和Linux内存管理机制,后续可以尝试将示例中的简单乘法器替换为更复杂的卷积神经网络或数字信号处理算法。