Zynq-7000 完整硬件平台构建与验证权威指南

前言

  • 本文目标: 本文依托 Alinx的AX7021B ZYNQ开发板,介绍如何使用Vivado搭建PS与PL的通信的通道,包含AXI DMA,BRAM,以及AXI GPIO,旨在为PS和PL通信提供一个完整的标准流程

项目概述

项目目标: 创建一个包含 BRAM 和 AXI 接口的基础 Zynq 硬件平台,并使用 Vitis 裸机程序验证其功能,最终实现基于 AXI CDMA 和中断的 PL-PS 数据回环,为后续 PetaLinux 开发奠定经过硬件验证的坚实基础。

开发环境:

  • Vivado 2023.2
  • Vitis 2023.2
  • 目标器件:XC7Z020-2CLG484I

第一阶段:Vivado 基础硬件平台设计与BRAM单向通路

创建 Vivado 工程

操作步骤:

  1. 启动 Vivado 2023.2,点击 Quick Start -> Create Project
  2. Project Name: 命名为 ZYNQ_7000_AXI_BRAM
  3. Project Location: 指定一个不包含中文、空格或特殊字符的路径
  4. Project Type: 选择 RTL Project 并勾选 Do not specify sources at this time

决策依据与原因:

  • 命名规范: 硬件开发工具链对特殊字符路径的兼容性较差,从源头避免能省去未来很多麻烦
  • 项目类型: 我们将采用自顶向下的设计方法,从一个空白的 Block Design 画布开始,通过集成 IP 核来构建系统

指定目标器件(关键步骤)

信息收集:

  • 芯片丝印: XC7Z020CLG484ABX1813 (提供了基础型号和封装)
  • 用户手册: XC7Z020-2CLG484I (提供了最关键的速度等级 -2 和温度等级 I)

决策: 我们必须使用用户手册提供的更详细的型号,因为速度等级 (-2) 对 Vivado 的时序分析至关重要。丝印信息仅用于交叉验证。

操作步骤:

  1. Default Part 页面,确保处于 Parts 标签页
  2. 使用 Filters (筛选器) 精确定位:
    • Family: Zynq-7000
    • Package: clg484
    • Speed: -2
  3. 从筛选后的列表中选择 xc7z020clg484-2,点击 NextFinish 完成工程创建

决策依据与原因:

  • 选择 Parts 而非 Boards 提供了最大的灵活性,适用于任何没有预装 BSP 的开发板
  • 准确的速度等级确保了 Vivado 时序分析的准确性,是硬件能否稳定运行的根本保证

构建 Block Design 与配置 ZYNQ7 PS

创建画布:

  1. Flow Navigator -> IP INTEGRATOR 下,点击 Create Block Design
  2. 使用默认名 design_1 创建
  3. 点击加号 + 图标,搜索并添加 ZYNQ7 Processing System IP 核

配置 ZYNQ7 PS: 双击 Zynq IP 核打开 Re-customize IP 窗口,依据手册信息逐项配置:

MIO Configuration (外设IO配置)

信息来源: 手册指出核心板 USB-UART 连接到 MIO 14/15 (UART 0),底板串口连接到 MIO 12/13 (UART 1);PS 端以太网连接到 MIO 16..27 和 MIO 52..53

配置操作:

  • 启用 UART 0 (分配到 MIO 14..15)
  • 启用 UART 1 (分配到 MIO 12..13)
  • 启用 ENET 0 (分配到 MIO 16..27MIO 52..53),并配置电平标准HSTL1.8V
  • 启用 GPIO 并分配到 MIO(以太网 PHY 复位信号连接到 PS_MIO7
  • Bank 0 IO Voltage 设为 LVCMOS33Bank 1 IO Voltage 设为 LVCMOS18

配置解释

HSTL1.8V vs LVCMOS18

特性 HSTL1.8V LVCMOS18
全称 High Speed Transceiver Logic 标准低功耗I/O
驱动能力 24-48mA 2-12mA
应用场景 高速接口(RGMII、DDR) 一般I/O(GPIO、UART)
阻抗匹配 50Ω,适合高频信号 阻抗要求宽松
信号完整性 优秀的噪声抑制 一般

为什么以太网需要HSTL1.8V:

  • RGMII接口工作频率高(125MHz)
  • 需要精确的时序和信号完整性
  • PHY芯片通常要求HSTL标准的驱动强度

Peripheral IO Pins(配置PHY Reset)

配置操作:

  • 勾选:GPIO MIO ->Ethernet Phy Reset,配置网口复位引脚

  • 管脚指定:设置为MIO 7(根据开发板手册)

配置解释

系统上电 → GPIO拉低PHY复位引脚 → 延时等待 → GPIO拉高释放复位 → PHY芯片内部初始化 → 寄存器配置完成 → MDIO通信建立 → 网卡可用

Clock Configuration (时钟配置)

信息来源: 手册指出一个 33.3333MHz 晶振提供给 PS 系统

配置操作:

  • Input Frequency (PS_CLK) 精确设置为 33.333333
  • 保持 CPU Clock Ratio6:2:1,这将使 CPU 运行在约 667MHz

DDR Configuration (DDR内存配置)

信息来源: 手册指出内存由两片美光 MT41K256M16TW-107 组成,总线宽度 32-bit,运行速度 533MHz (1066 MT/s)。开发板实际焊接的是兼容型号海力士 H5TQ4G63AFR-PBI

决策: 由于 Vivado 的 Memory Part 列表中没有海力士的型号,我们采纳手册的建议,使用兼容性最好的美光型号进行配置

配置操作:

  1. Memory Part 下拉列表中,选择最接近的 MT41K256M16 RE-125
  2. Effective DRAM Bus Width 确认选择 32 Bit
  3. 其他时序参数(CAS Latency等)由 Vivado 根据所选型号自动填充,无需修改

点击 OK 保存 PS 配置。运行Block Automation连接DDR和FIXED_IO。至此,一个最小化的、可引导的 Zynq 核心系统构建完成。


第二阶段:BRAM回环数据通路搭建

目标: 在已验证的基础硬件平台上,实现BRAM回环数据通路

硬件平台设计 (Vivado)

添加、配置并连接 PL 端 BRAM

操作步骤:

  1. 在画布上添加 AXI BRAM ControllerBlock Memory Generator
  • 【技术原理补充】为什么需要这两个IP核?

    • Block Memory Generator (BMG):

      • 本质: 纯粹的存储资源,是 FPGA 内部 Block RAM 的封装

      • 接口: 提供原生的 BRAM 接口(地址、数据、使能、写使能等信号)

      • 特点: 没有总线协议概念,不能直接连接到 AXI 总线

      • 类比: 相当于一个”裸露”的存储芯片

    • AXI BRAM Controller (ABC):

      • 本质: AXI 总线协议转换器
        • 功能:
          • 作为 AXI Slave,响应来自 PS 的总线访问
          • 将 AXI 读写事务转换为 BRAM 的原生控制信号
          • 处理 AXI 协议的握手、突发传输、错误响应等复杂逻辑
      • 类比:相当于存储芯片的”控制器芯片
  • 完整的 PS 访问 BRAM 数据流程

    第1步:PS 发起访问
    PS CPU 执行: Xil_Out32(0x40000000, data)

    生成 AXI Write 事务 (地址 + 数据 + 控制信号)

    第2步:AXI 总线路由
    AXI SmartConnect 根据地址译码

    将事务路由到 AXI BRAM Controller 的 S_AXI 接口

    第3步:协议转换
    AXI BRAM Controller 接收 AXI 事务

    解析 AXI 协议:地址、数据、字节使能等

    转换为 BRAM 原生信号:

    • bram_addr = AXI地址的低位部分

    • bram_din = AXI写数据

    • bram_we = 根据AXI写使能生成

    • bram_en = 1 (使能BRAM)

    第4步:BRAM 存储操作
    Block Memory Generator 接收控制信号

    将数据写入指定地址的存储单元

    第5步:响应返回
    AXI BRAM Controller 向 PS 发送 AXI Write Response

    PS 收到写操作完成确认

  1. 配置 Block Memory Generator:
    • Mode: 设为 BRAM Controller (让其与控制器自动匹配接口)
    • Memory Type: 设为 True Dual Port RAM (为未来扩展保留双端口)
    • Port A Options -> Write Depth: 设为 2048 (创建 8KB 容量)
  2. 配置 AXI BRAM Controller:
    • 确认 Number of BRAM Interfaces1,以确保只使用 BRAM 的一个端口,释放另一个端口
  3. 自动化连接:
    • 点击 Run Block Automation 连接 PS 的 DDR 和 FIXED_IO
    • 点击 Run Connection Automation 连接 PS、AXI 总线和 BRAM。Vivado 会自动添加 AXI SmartConnectProcessor System Reset IP
  4. 地址分配:
    • 切换到 Address Editor 标签页,为 /axi_bram_controller_0 分配一个地址(如 0x40000000),并确认其 Range8K

决策依据与原因: 我们搭建了一条从 PS 到 PL 的完整数据通路。PS (Master) -> AXI总线 -> BRAM控制器 (Slave) -> BRAM。为 AXI BRAM Controller 分配地址,是让 PS 能够通过 AXI 总线访问 BRAM 的关键

添加 CDMA 与中断逻辑实现回环通路

操作步骤:

  1. 添加第二个 BRAM 控制器: 点击 + 添加 AXI BRAM Controller (axi_bram_ctrl_1)。双击确认其 Number of BRAM Interfaces1

    PS → AXI BRAM Controller #0 → BRAM Port A (作为数据源)

    PS → AXI BRAM Controller #1 → BRAM Port B (作为数据目标)

    CDMA → 可同时访问两个控制器 → 实现高效数据搬运

  2. 添加 CDMA 引擎: 点击 + 添加 AXI CDMA (axi_cdma_0)。双击并取消勾选 Enable Scatter Gather

    • 什么是 AXI CDMA? CDMA (Central Direct Memory Access):

      • 本质: 专用的数据搬运引擎,独立于 CPU 运行
      • 角色: 在 AXI 总线上既是 Master(发起数据访问)又有 Slave 接口(接收配置命令)
      • 核心价值: 将 CPU 从繁重的数据拷贝任务中解放出来
    • 数据流程

      系统数据流设计:

      1. PS 配置阶段:

        PS (AXI Master) → CDMA S_AXI_LITE (配置接口)

        配置:源地址、目标地址、传输长度

      2. 数据搬运阶段:

        CDMA M_AXI (数据Master) → AXI总线 → BRAM Controller #0 (读取数据)

        CDMA M_AXI (数据Master) → AXI总线 → BRAM Controller #1 (写入数据)

      3. 完成通知阶段: CDMA → 中断信号 → PS (任务完成通知)

    • 为什么禁用 Scatter Gather?

      Scatter Gather 模式:

      • 功能: 支持复杂的、非连续的内存访问模式
      • 适用场景: 需要在多个分散的内存块间进行复杂数据重组
      • 成本: 需要额外的描述符内存、更复杂的控制逻辑

      Simple Transfer 模式(我们的选择):

      • 功能: 点对点的连续数据传输
      • 适用场景: 简单的内存拷贝、缓冲区交换
  3. 添加中断合并器: 点击 + 添加 Concat (xlconcat_0)。双击并设置 Number of Ports1

    • 【技术原理补充】中断合并器的系统作用

      • Zynq 中断系统硬件基础
        • PS 端有 IRQ_F2P[15:0] 共 16 条从 PL 到 PS 的中断线,每条中断线都是 1 位宽度,互相独立,每条线只能连接一个信号源
        • Concat (xlconcat) 的作用是将多个 1 位输入信号按顺序拼接成一个多位输出信号, 输入和输出位宽相等,Concat 不压缩信息,只是重新组织信号
      • 什么时候需要 Concat?
        • 节省连接线: 在复杂设计中整理信号
        • 信号重组: 将分散的中断信号组织成总线形式
        • 接口匹配: 某些 IP 需要总线形式的中断输入
      • 大量中断源的解决方案,当中断源大于十六时,使用AXI Interrupt Controller
    • 为什么当前只配置 1 个端口?

      当前系统分析:

      • 只有 CDMA 一个中断源
      • 理论上可以直接连接到 PS

决策依据与原因:

  • axi_bram_ctrl_1: 用于连接 True Dual Port RAM 的第二个端口 (PORTB),创建独立的读出通道
  • AXI CDMA: 这是实现硬件数据搬运的核心,它能作为独立的 AXI Master,在不同地址间传输数据,从而将 CPU 从繁重的拷贝任务中解放出来。禁用 Scatter Gather 是为了节省资源,因为我们只需要简单的点对点传输
  • Concat: 这是连接 PL 中断源到 PS 中断端口的标准工程实践。即使只有一个中断源,使用它也能保证设计的规范性和未来扩展性

使能并连接中断通路(关键步骤)

操作步骤:

  1. 使能 PS 中断端口: 双击 Zynq IP 核,进入 Interrupts -> Fabric Interrupts,勾选 IRQ_F2P[15:0],点击 OK。此时 Zynq IP 上会出现 IRQ_F2P 端口
  2. 自动连接 AXI 总线: 点击 Run Connection Automation,勾选所有新添加的 IP(axi_bram_ctrl_1axi_cdma_0),让 Vivado 自动连接时钟、复位和数据总线
  3. 手动连接数据与中断:
    • blk_mem_gen_0BRAM_PORTB 连接到 axi_bram_ctrl_1BRAM_PORTA
    • axi_cdma_0cdma_introut 连接到 xlconcat_0In0
    • xlconcat_0dout 连接到 Zynq IP 的 IRQ_F2P[0:0]

决策依据与原因:

  • 我们必须先在 Zynq 内部”打开”接收中断的大门,对应的物理端口才会出现
  • 我们构建了一条完整的中断信号物理链路:CDMA (事件发生) -> Concat (信号捆绑) -> Zynq PS (信号接收)。这是实现中断功能的硬件基础

分配多视角地址

操作步骤:

  1. 切换到 Address Editor 标签页
  2. 配置 PS 视角: 确保下拉菜单为 /processing_system7_0/Data,右键点击空白处并选择 Assign All
  3. 配置 CDMA 视角: 切换下拉菜单为 /axi_cdma_0/Data,再次 Assign All

决策依据与原因: 地址映射是基于”访问者”的视角。

  • PS 视角: PS 作为主控制器,需要知道所有外设的地址,以便配置它们(配置 CDMA)和与它们交换数据(读写两个 BRAM)
  • CDMA 视角: CDMA 作为数据搬运工,它只需要知道”从哪搬”(源 BRAM 地址)和”往哪搬”(目标 BRAM 地址)。它不需要知道自己的配置地址,因此其 S_AXI_LITE 接口在此视图中被正确地 Excluded

生成最终硬件平台

操作步骤:

  1. Validate Design (F6): 确保所有连接和配置无误
  2. Create HDL Wrapper: 更新顶层封装
  3. Generate Bitstream: 生成最终的比特流
  4. Export Hardware: 导出包含最新设计和比特流的 .xsa 文件

决策依据与原因: 至此,我们得到了一份经过完整硬件设计、包含高级功能的硬件平台文件,它是我们进行最终软件验证的”黄金标准版”蓝图。


Vitis 完整功能验证(BRAM回环验证)

目标: 编写一个综合性的裸机程序,验证 CDMA 数据回环和中断通知机制的正确性。

更新 Vitis 工程硬件规格

操作步骤:

  1. 若无 Vitis 工程,则从 Vivado 启动 Vitis IDE 并创建一个
  2. 若已有工程,则在 Vitis 中双击 Platform 的 platform.spr 文件,更新硬件规格 (Hardware Specification) 为我们最新导出的 .xsa 文件
  3. 右键 Platform 工程,选择 Build Project,强制 Vitis 重新解析硬件并生成 BSP

决策依据与原因: 这是解决我们在调试中遇到的**”软硬件不匹配”**问题的根本方法。必须确保 Vitis 的 BSP 是基于最终的、正确的 .xsa 文件生成的。

编写完整验证代码

操作步骤:

  1. app_component/src 目录下,用以下经过最终验证的 main.c 代码替换原有内容
  2. 右键应用工程 -> Build Project

关键代码修正与说明:

  • 手动定义 BRAM 地址: 由于 Vitis BSP 不为 BRAM Controller 生成宏,我们根据 Device Tree 和 Address Editor 的信息,在代码中手动定义 BRAM_A_BASE_ADDRBRAM_B_BASE_ADDR
  • 使用确切的宏: DEVICE_ID 和中断相关的掩码宏,全部使用从官方 xaxicdma_hw.h 头文件中确认的、确切的名称,如 XAXICDMA_XR_IRQ_ALL_MASK
  • 中断号: 中断ID CDMA_INTR_ID 直接使用 XPAR_FABRIC... 宏,这是最可靠的方式
  • 中断处理: 采用最经典、最兼容的模式:自定义中断处理函数 CdmaIntrHandler,并将其直接连接到 GIC。函数内部通过 XAxiCdma_IntrGetStatusXAxiCdma_IntrAck 进行状态获取和清除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/******************************************************************************
*
* Copyright (C) 2010 - 2023, Xilinx, Inc. All rights reserved.
* SPDX-License-Identifier: MIT
*
* 这是经过源码验证的 AXI CDMA 回环测试代码,使用直接寄存器访问
* 处理中断,适配 Vitis 2023.2 驱动行为。
*
******************************************************************************/

/***************************** Include Files *********************************/
#include "xparameters.h"
#include "xaxicdma.h"
#include "xaxicdma_hw.h" // ** 必须包含此文件以获取寄存器偏移量和掩码 **
#include "xscugic.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "xil_cache.h"
#include "xstatus.h"
#include <stdio.h>

/************************** Constant Definitions *****************************/

// --- 手动定义缺失的 BRAM 控制器基地址 ---
#define BRAM_A_BASE_ADDR 0x40000000
#define BRAM_B_BASE_ADDR 0x40001000

// --- 根据您的 xparameters.h 确定的确切基地址 ---
#define CDMA_BASE_ADDR XPAR_AXI_CDMA_0_BASEADDR
#define INTC_BASE_ADDR XPAR_XSCUGIC_0_BASEADDR

// --- Zynq-7000 PL->PS 中断号 IRQ_F2P[0] 固定为 61 ---
#define CDMA_INTR_ID 61

#define DATA_SIZE 10

/************************** Variable Definitions *****************************/
static XAxiCdma AxiCdmaInstance;
static XScuGic IntcInstance;

static volatile int TransferDone;
static volatile int TransferError;

/************************** Function Prototypes ******************************/
int SetupCdma(void);
int SetupInterruptSystem(void);
static void CdmaIntrHandler(void *CallbackRef);

/*****************************************************************************/
int main(void)
{
int Status;
u8 SourceBuffer[DATA_SIZE];
u8 DestBuffer[DATA_SIZE];

// 重新初始化缓存系统以确保一致性
Xil_DCacheDisable();
Xil_DCacheEnable();

xil_printf("\r\n--- AXI CDMA Loopback Test (Direct Register Access Version) ---\r\n");

u8 *SourceBram = (u8 *)BRAM_A_BASE_ADDR;
u8 *DestBram = (u8 *)BRAM_B_BASE_ADDR;

Status = SetupCdma();
if (Status != XST_SUCCESS) {
xil_printf("CDMA setup failed!\r\n");
return XST_FAILURE;
}

Status = SetupInterruptSystem();
if (Status != XST_SUCCESS) {
xil_printf("Interrupt setup failed!\r\n");
return XST_FAILURE;
}

xil_printf("Preparing source data and writing to BRAM_A...\r\n");
for (int i = 0; i < DATA_SIZE; i++) {
SourceBuffer[i] = i + 0xA0;
SourceBram[i] = SourceBuffer[i];
}
Xil_DCacheFlushRange((UINTPTR)SourceBram, DATA_SIZE);

xil_printf("Starting CDMA Simple Transfer...\r\n");
TransferDone = 0;
TransferError = 0;

Status = XAxiCdma_SimpleTransfer(&AxiCdmaInstance, (UINTPTR)SourceBram,
(UINTPTR)DestBram, DATA_SIZE, NULL, NULL);
if (Status != XST_SUCCESS) {
xil_printf("CDMA SimpleTransfer failed with status: %d\r\n", Status);
return XST_FAILURE;
}

xil_printf("Waiting for interrupt...\r\n");
while (!TransferDone && !TransferError) { /* Wait */ }

if (TransferError) {
xil_printf("CDMA transfer finished with an error!\r\n");
return XST_FAILURE;
} else {
xil_printf("CDMA transfer finished successfully!\r\n");
xil_printf("Reading data back from BRAM_B for verification...\r\n");
Xil_DCacheInvalidateRange((UINTPTR)DestBram, DATA_SIZE);

for (int i = 0; i < DATA_SIZE; i++) {
DestBuffer[i] = DestBram[i];
}

for (int i = 0; i < DATA_SIZE; i++) {
if (SourceBuffer[i] != DestBuffer[i]) {
xil_printf("Verification FAILED at index %d! Sent %02X, Got %02X\r\n",
i, SourceBuffer[i], DestBuffer[i]);
return XST_FAILURE;
}
}
xil_printf("Data verification PASSED!\r\n");
}

xil_printf("--- Test Finished ---\r\n");
return XST_SUCCESS;
}

/*****************************************************************************/
int SetupCdma(void)
{
XAxiCdma_Config *CdmaCfg;
int Status;

// 使用基地址查找配置,避免依赖可能不存在的 DEVICE_ID 宏
CdmaCfg = XAxiCdma_LookupConfig(CDMA_BASE_ADDR);
if (!CdmaCfg) {
xil_printf("No CDMA config found for base address 0x%08X\r\n", CDMA_BASE_ADDR);
return XST_FAILURE;
}

Status = XAxiCdma_CfgInitialize(&AxiCdmaInstance, CdmaCfg, CdmaCfg->BaseAddress);
if (Status != XST_SUCCESS) {
xil_printf("CDMA initialization failed with status: %d\r\n", Status);
return XST_FAILURE;
}

// 验证 Simple Mode
if (!XAxiCdma_IsSimpleMode(&AxiCdmaInstance)) {
xil_printf("CDMA is NOT in Simple Mode - check Vivado configuration!\r\n");
return XST_FAILURE;
}
xil_printf("CDMA is in Simple Mode\r\n");

// 使能所有类型的中断
XAxiCdma_IntrEnable(&AxiCdmaInstance, XAXICDMA_XR_IRQ_ALL_MASK);

return XST_SUCCESS;
}

/*****************************************************************************/
int SetupInterruptSystem(void)
{
int Status;
XScuGic_Config *IntcConfig;

// 使用基地址查找配置,避免依赖可能不存在的 DEVICE_ID 宏
IntcConfig = XScuGic_LookupConfig(INTC_BASE_ADDR);
if (NULL == IntcConfig) {
xil_printf("No GIC config found for base address 0x%08X\r\n", INTC_BASE_ADDR);
return XST_FAILURE;
}

Status = XScuGic_CfgInitialize(&IntcInstance, IntcConfig, IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
xil_printf("GIC initialization failed with status: %d\r\n", Status);
return XST_FAILURE;
}

Status = XScuGic_Connect(&IntcInstance, CDMA_INTR_ID,
(Xil_InterruptHandler)CdmaIntrHandler,
(void *)&AxiCdmaInstance);
if (Status != XST_SUCCESS) {
xil_printf("GIC connect failed with status: %d\r\n", Status);
return XST_FAILURE;
}

XScuGic_Enable(&IntcInstance, CDMA_INTR_ID);

Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
&IntcInstance);
Xil_ExceptionEnable();

xil_printf("Interrupt system setup completed\r\n");
return XST_SUCCESS;
}

/*****************************************************************************/
/**
* 中断处理函数,使用直接寄存器访问方式
* 这是为了匹配 Vitis 2023.2 驱动的实际行为
******************************************************************************/
static void CdmaIntrHandler(void *CallbackRef)
{
u32 IntrStatus;
XAxiCdma *CdmaInstancePtr = (XAxiCdma *)CallbackRef;

// --- 关键修正: 直接读写寄存器 ---

// 1. 读取中断状态寄存器 (SR) 来获取中断状态
// 使用 xaxicdma_hw.h 中定义的 XAxiCdma_ReadReg 宏
IntrStatus = XAxiCdma_ReadReg(CdmaInstancePtr->BaseAddr, XAXICDMA_SR_OFFSET);

// 2. 清除已触发的中断 (通过向状态寄存器(SR)写回对应位来清除)
// 使用 xaxicdma_hw.h 中定义的 XAxiCdma_WriteReg 宏
XAxiCdma_WriteReg(CdmaInstancePtr->BaseAddr, XAXICDMA_SR_OFFSET,
IntrStatus & XAXICDMA_XR_IRQ_ALL_MASK);

// 3. 检查具体是哪种中断
if (IntrStatus & XAXICDMA_XR_IRQ_ERROR_MASK) {
xil_printf("CDMA Error Interrupt: 0x%08X\r\n", IntrStatus);
TransferError = 1;
}

if (IntrStatus & XAXICDMA_XR_IRQ_IOC_MASK) {
xil_printf("CDMA Transfer Complete Interrupt Received!\r\n");
TransferDone = 1;
}
}

关键代码改进说明:

  1. 使用基地址而非设备ID

    • 原因: 某些BSP生成过程中可能不会生成设备ID宏,使用基地址更可靠

    • 改进: XAxiCdma_LookupConfig(CDMA_BASE_ADDR) 而非依赖可能不存在的设备ID

  2. 直接寄存器访问处理中断

    • 原因: 匹配Vitis 2023.2驱动的实际底层行为,更稳定可靠

    • 改进: 使用 XAxiCdma_ReadReg/WriteReg 直接操作CDMA状态寄存器

  3. 固定中断号使用

    • 原因: Zynq-7000 的 IRQ_F2P[0] 中断号在所有系统中都是固定的61

    • 改进: 直接使用 #define CDMA_INTR_ID 61 避免依赖生成的宏

  4. 增强的缓存处理

    • 原因: 确保DMA传输前后的数据一致性

    • 改进: 在程序开始时重新初始化缓存系统

  5. 更细粒度的数据类型

    • 原因: 使用字节级操作可以更精确地验证DMA传输

    • 改进: 使用 u8 数组而非 u32,提高测试覆盖率

决策依据与原因: 这份代码解决了Vitis 2023.2工具链中的几个关键问题:

  1. BSP自动生成宏的不确定性
  2. 驱动API在不同版本间的兼容性差异
  3. 中断处理机制的底层实现细节
  4. 缓存一致性在DMA操作中的重要性

这是经过实际验证、针对您的硬件平台和开发环境优化的最终版本。

最终硬件验证

操作步骤:

  1. 连接开发板的 JTAG 和 UART 端口
  2. 在 Vitis 中,右键应用工程 -> Launch -> Launch on Hardware

预期结果: 在 Vitis 的串口终端 (Serial Terminal) 中,将按顺序打印出以下信息:

1
2
3
4
5
6
7
8
9
10
--- AXI CDMA Loopback Test ---
CDMA is in Simple Mode
Preparing source data and writing to BRAM_A...
Starting CDMA Simple Transfer...
Waiting for interrupt...
CDMA Transfer Complete Interrupt Received!
CDMA transfer finished successfully!
Reading data back from BRAM_B for verification...
Data verification PASSED!
--- Test Finished ---

最终结论: 项目成功! 打印出 Data verification PASSED! 意味着我们设计的整个系统——从 PS 的 DDR,到 AXI 总线,到 PL 端的 BRAM 和 CDMA,再到中断返回 PS——形成了一个完美闭环。这份 .xsa 硬件平台现在可以被认为是完全经过验证、100% 可靠的,可以充满信心地交付给 PetaLinux 进行后续的系统开发。


第三阶段: AXI_GPIO控制PL端KEY与LED

目标: 在已验证的 BRAM+CDMA 硬件平台基础上,进一步集成 AXI GPIO IP 核,实现由 PL 端物理按键触发中断,PS 端软件响应并控制 PL 端 LED 亮灭的完整 I/O 闭环。此阶段将深入验证 PL-PS 中断合并、软件中断处理及 GPIO 驱动的使用。

硬件平台扩展 (Vivado)

添加并配置 AXI_GPIO IP 核

操作步骤:

  1. 在已有的 Block Design 画布中,点击 + 添加 AXI GPIO IP 核 (axi_gpio_0)。
  2. 双击该 IP 核,打开 Re-customize IP 窗口进行精确配置:
    • 勾选 Enable Dual Channel,为输入和输出创建独立通道。
    • 通道 1 (GPIO):
      • 勾选 All Inputs
      • GPIO Width 设置为 1。(用于连接单个按键)
    • 通道 2 (GPIO2):
      • 勾选 All Outputs
      • GPIO Width 设置为 1。(用于控制单个 LED)
    • 勾选 Enable Interrupt,为 IP 核添加中断输出端口。

决策依据与原因:

  • 双通道: 将输入(按键)和输出(LED)在逻辑上分离,使驱动代码更清晰,每个通道可以独立控制。
  • 精确位宽: 将位宽设为 1,与我们的物理目标(1个按key,1个LED)完全匹配,可以最大化地节省 PL 逻辑资源,并使设计意图明确化。
  • 使能中断: 这是实现“按键按下,软件才响应”这一低功耗、高效率模式的硬件基础。

集成 GPIO 到系统并连接中断

操作步骤:

  1. 连接 AXI 总线: 点击 Run Connection Automation,在弹出的窗口中仅勾选新添加的 /axi_gpio_0,让 Vivado 自动完成其 S_AXI 从接口、时钟和复位信号的连接。
  2. 扩展并连接中断:
    • 双击项目中已有的 xlconcat_0 IP 核,将其 Number of Ports1 修改为 2
    • 确认 CDMA 的中断输出仍连接在 In0 端口。
    • axi_gpio_0ip2intc_irpt 输出端口,手动连接到 xlconcat_0In1 输入端口。
  3. 引出物理端口:
    • 右键点击 axi_gpio_0 上的 gpio_io_i 端口 -> Create Port... -> 命名为 key_pl
    • 右键点击 axi_gpio_0 上的 gpio2_io_o 端口 -> Create Port... -> 命名为 led_pl
  4. 分配地址:
    • 切换到 Address Editor,在 /processing_system7_0/Data 视图下,为新出现的 /axi_gpio_0 分配地址(如 0x41200000)。

决策依据与原因:

  • 中断合并: 扩展 xlconcat 而非新增,是处理多中断源的标准工程实践,保证了系统的可扩展性,并维持了原有 CDMA 中断通路的完整性。
  • 引出端口: Create Port 是将 Block Design 内部的逻辑信号“暴露”到顶层设计的唯一方法,是后续进行物理引脚约束的前提。
  • 分配地址: 为 AXI GPIO 分配地址,是让 PS 能够通过总线找到并配置它的“门牌号”。

分配物理引脚约束 (XDC)

信息来源: 核心板原理图指出,目标按键连接到 FPGA 的 A17 引脚,目标 LED 连接到 A16 引脚,其 IO Bank 供电为 3.3V。

操作步骤:

  1. Flow Navigator 中,点击 Open Implemented Design
  2. 在 Vitis 顶部菜单栏选择 Window -> I/O Ports 打开引脚分配窗口。
  3. 找到 key_pl 端口:
    • Package Pin: 输入 A17
    • I/O Std: 选择 LVCMOS33
  4. 找到 led_pl 端口:
    • Package Pin: 输入 A16
    • I/O Std: 选择 LVCMOS33
  5. Ctrl + S 保存,将约束写入项目的 XDC 文件中。

决策依据与原因: 这是连接逻辑设计与物理现实的关键一步。Package Pin 确保信号能驱动正确的外部硬件,I/O Std 确保电气电平兼容,防止器件损坏。

生成最终硬件平台

操作步骤:

  1. Validate Design (F6): 检查最终设计。
  2. Create HDL Wrapper: 更新顶层封装。
  3. Generate Bitstream: 生成包含所有硬件逻辑(CDMA + GPIO)的比特流。
  4. Export Hardware: 导出包含最新设计和比特流的 .xsa 文件,作为 Vitis 软件开发的输入。

软件验证与协同调试 (Vitis Unified IDE)

创建并配置调试会话

操作步骤:

  1. 更新 Vitis Platform 的硬件规格,指向最新导出的 .xsa 文件,并重建 BSP。
  2. 在左侧活动栏进入 Run and Debug 视图。
  3. 点击齿轮图标 ⚙️ 创建或修改 launch.json 文件。
  4. 在图形化配置页面,确保以下关键项被正确设置:
    • Program Device 必须勾选。
    • Bitstream File 指向包含 GPIO 设计的最新 .bit 文件。
    • APPLICATION 指向您编译的 GPIO 测试程序的 .elf 文件。

决策依据与原因: Program Device 是确保 PL 端硬件(包括 AXI GPIO 和 ILA)在软件运行前被正确配置的核心开关。Vitis 2023.2 会基于此设置,在后台自动查找并关联同名的 .ltx 探针文件。

GPIO 中断验证 C 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/***************************** Include Files *********************************/
#include "xparameters.h"
#include "xparameters_ps.h"
#include "xgpio.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "sleep.h" // 添加延时函数支持

/************************** Constant Definitions *****************************/
#define GPIO_BASEADDR XPAR_XGPIO_0_BASEADDR
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define BUTTON_CHANNEL 2 // 按键在通道2
#define LED_CHANNEL 1 // LED在通道1
#define GPIO_INTERRUPT_ID XPS_FPGA1_INT_ID // 62

// 去抖参数
#define DEBOUNCE_DELAY_MS 30 // 去抖延时50毫秒
#define DEBOUNCE_COUNT 3 // 连续稳定读取次数

/************************** Variable Definitions *****************************/
XGpio Gpio;
XScuGic IntcInstance;

static volatile u32 led_state = 0; // LED状态:0=熄灭,1=点亮
static volatile u32 debounce_flag = 0; // 去抖标志:1=正在去抖中

/************************** Function Prototypes ******************************/
int SetupGpio(void);
int SetupInterrupts(void);
void GpioIntrHandler(void *CallbackRef);
int IsButtonPressed(void); // 按键去抖检测函数

/*****************************************************************************/
int main(void)
{
int Status;

xil_printf("\r\n=== 带去抖功能的按键LED切换系统 ===\r\n");
xil_printf("功能:按键触发中断 → 去抖检测 → LED切换\r\n");

// 初始化GPIO
Status = SetupGpio();
if (Status != XST_SUCCESS) {
xil_printf("❌ GPIO初始化失败\r\n");
return XST_FAILURE;
}

// 设置中断系统
Status = SetupInterrupts();
if (Status != XST_SUCCESS) {
xil_printf("❌ 中断设置失败\r\n");
return XST_FAILURE;
}

xil_printf("✅ 系统就绪,请按按键测试...\r\n");

// 主循环
while(1) {
// 什么都不做,等待中断
}

return XST_SUCCESS;
}

/*****************************************************************************/
int SetupGpio(void)
{
XGpio_Config *ConfigPtr;
int Status;

xil_printf("--- GPIO初始化 ---\r\n");

ConfigPtr = XGpio_LookupConfig(GPIO_BASEADDR);
if (ConfigPtr == NULL) {
return XST_FAILURE;
}

Status = XGpio_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

// 配置GPIO方向
XGpio_SetDataDirection(&Gpio, BUTTON_CHANNEL, 0xFFFFFFFF); // 按键输入
XGpio_SetDataDirection(&Gpio, LED_CHANNEL, 0x00000000); // LED输出

// 设置LED初始状态为熄灭
led_state = 0;
XGpio_DiscreteWrite(&Gpio, LED_CHANNEL, led_state);
xil_printf("LED初始状态: 熄灭\r\n");

// 配置GPIO中断
XGpio_InterruptClear(&Gpio, XGPIO_IR_CH2_MASK);
XGpio_InterruptEnable(&Gpio, XGPIO_IR_CH2_MASK);
XGpio_InterruptGlobalEnable(&Gpio);

xil_printf("✅ GPIO配置完成\r\n");
return XST_SUCCESS;
}

/*****************************************************************************/
int SetupInterrupts(void)
{
XScuGic_Config *IntcConfig;
int Status;

xil_printf("--- 中断设置 ---\r\n");

// 初始化中断控制器
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (IntcConfig == NULL) {
return XST_FAILURE;
}

Status = XScuGic_CfgInitialize(&IntcInstance, IntcConfig, IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

// 设置异常处理
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
&IntcInstance);
Xil_ExceptionEnable();

// 设置中断优先级和触发类型
XScuGic_SetPriorityTriggerType(&IntcInstance, GPIO_INTERRUPT_ID, 0xA0, 0x3);

// 连接中断处理函数
Status = XScuGic_Connect(&IntcInstance, GPIO_INTERRUPT_ID,
(Xil_ExceptionHandler)GpioIntrHandler,
(void *)&Gpio);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

// 使能中断
XScuGic_Enable(&IntcInstance, GPIO_INTERRUPT_ID);

xil_printf("✅ 中断设置完成 (ID: %d)\r\n", GPIO_INTERRUPT_ID);
return XST_SUCCESS;
}

/*****************************************************************************/
// 按键去抖检测函数
int IsButtonPressed(void)
{
int i;
u32 button_state;
int stable_count = 0;

// 连续检测按键状态
for (i = 0; i < DEBOUNCE_COUNT; i++) {
button_state = XGpio_DiscreteRead(&Gpio, BUTTON_CHANNEL);
if (button_state == 0) { // 按键按下(假设按下为0)
stable_count++;
}
usleep(10000); // 延时10毫秒
}

// 如果连续检测都是按下状态,认为是有效按键
return (stable_count == DEBOUNCE_COUNT);
}

/*****************************************************************************/
void GpioIntrHandler(void *CallbackRef)
{
XGpio *GpioPtr = (XGpio *)CallbackRef;

// 清除中断
XGpio_InterruptClear(GpioPtr, XGPIO_IR_CH2_MASK);

// 如果正在去抖处理中,直接返回
if (debounce_flag) {
XGpio_InterruptEnable(GpioPtr, XGPIO_IR_CH2_MASK);
return;
}

// 设置去抖标志
debounce_flag = 1;

// 延时去抖
usleep(DEBOUNCE_DELAY_MS * 1000); // 转换为微秒

// 检测按键是否真的被按下
if (IsButtonPressed()) {
// 切换LED状态
led_state = !led_state;
XGpio_DiscreteWrite(GpioPtr, LED_CHANNEL, led_state);

xil_printf("💡 LED切换为: %s\r\n", led_state ? "点亮" : "熄灭");

// 等待按键释放,避免连续触发
while (XGpio_DiscreteRead(GpioPtr, BUTTON_CHANNEL) == 0) {
usleep(10000); // 10毫秒检测一次
}
usleep(50000); // 按键释放后再等待50毫秒
}

// 清除去抖标志
debounce_flag = 0;

// 重新使能中断
XGpio_InterruptEnable(GpioPtr, XGPIO_IR_CH2_MASK);
}

新版Vitis中断配置问题

Vitis 2023.2 API变化

重要变化:设备初始化方式改变

1
2
3
4
5
6
// ❌ 旧版本方式(不再适用)
XGpio_Initialize(&Gpio, DEVICE_ID);

// ✅ 新版本方式(Vitis 2023.2+)
ConfigPtr = XGpio_LookupConfig(XPAR_XGPIO_0_BASEADDR); // 使用BASEADDR
XGpio_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddress);

中断ID映射关键点

1
2
3
4
5
6
// 必须包含PS中断定义
#include "xparameters_ps.h"

// 正确的中断ID映射
#define GPIO_INTERRUPT_ID XPS_FPGA1_INT_ID // 62 (IRQ_F2P[1])
// 如果连接到IRQ_F2P[0],则使用 XPS_FPGA0_INT_ID (61)

硬件连接验证

  • AXI GPIO ip2intc_irpt → xlconcat In1 → PS IRQ_F2P[1]
  • 对应中断ID:62 (XPS_FPGA1_INT_ID)

第四阶段: 构建并验证高性能AXI-Stream数据通路

目标: 在前序硬件平台基础上,集成 AXI DMA,构建一个连接 PS-DDR 与 PL 的高性能 AXI-Stream 数据通路。通过实现一个带缓冲的硬件回环,并编写一份功能完整、符合 Vitis 2023.2 SDT 规范的裸机程序,深度验证该数据通路的正确性、稳定性与中断机制。

核心理念: 本阶段从“功能实现”迈向“性能与鲁棒性设计”。每一个新增的IP和配置决策,都旨在解决一个潜在的系统瓶颈或时序风险,并确保软件验证的全面性。


硬件平台升级:从内存映射到数据流 (Vivado)

在此阶段,我们进行一次重大的架构升级,引入**数据流(Stream)**的概念,其核心是构建一条从内存出发,经过PL,再回到内存的高速公路。

关键IP的添加与配置

AXI Direct Memory Access (DMA) IP核

操作步骤:

  1. 在 Block Design 画布中,点击 + 添加 AXI Direct Memory Access IP核。
  2. 双击 IP 核进行配置 (Re-customize IP):
    • 取消勾选 Enable Scatter Gather Engine: 我们采用简单的点对点连续数据传输(Simple Mode),禁用此高级功能可节省大量逻辑资源并简化软件。
    • Width of Buffer Length Register 修改为 23: 将单次最大传输长度从默认的16KB提升至8MB,为处理大数据块提供充足的裕量。
    • 保持 Enable Write Channel (S2MM)Enable Read Channel (MM2S) 勾选,因为我们需要完整的读写双向通路。

【技术原理补充】为什么是AXI DMA,而不是AXI CDMA?

  • 角色不同: AXI CDMA内存到内存 的搬运工,工作在地址世界。而 AXI DMA内存与数据流 之间的桥梁,它能将内存中的数据打包成 AXI-Stream 数据流(MM2S),也能将数据流解包存回内存(S2MM)。它是连接两个世界的唯一通道。
  • 我们的需求: 要搭建 AXI-Stream 通路,AXI DMA 是必选项。

AXI4-Stream Data FIFO IP核 (关键优化)

操作步骤:

  1. 点击 + 添加 AXI4-Stream Data FIFO IP核。
  2. 双击进行配置:
    • 保持 FIFO Depth 为默认的 5121024
    • 其他参数如 TDATA Width 保持 Auto,它会自动匹配连接的 AXI-Stream 总线宽度。

【决策依据与原因】为什么要在回环中插入一个FIFO?
这是一个从“能用”到“好用”的关键决策。直接将DMA的输出M_AXIS_MM2S连接到输入S_AXIS_S2MM在功能上可行,但存在风险:

  1. 时序风险: TVALID/TREADY 握手信号路径过长,可能导致时序违规。FIFO作为一个独立的、带寄存器的模块,能将这条关键路径切断,提供时序隔离,极大提升系统稳定性。
  2. 流控缓冲: AXI DMA的读写引擎是两个独立的模块。在某些极端情况下,读引擎(MM2S)可能瞬间高速发送数据,而写引擎(S2MM)恰好有微小延迟。FIFO提供了数据缓冲区,能平滑这种速率上的微小不匹配,防止数据丢失或总线死锁。

高性能系统架构连接

使能并连接高性能HP接口(关键优化)

操作步骤:

  1. 双击 ZYNQ7 Processing System IP核。
  2. 导航至 PS-PL Configuration -> HP Slave AXI Interface
  3. 勾选 S AXI HP0 interface 并点击 OK。Zynq IP上会出现一个新的64位从端口 S_AXI_HP0
  4. 运行 Run Connection Automation,在为 axi_dma_0M_AXI_MM2SM_AXI_S2MM 接口选择主接口时,手动指定或确保 Vivado 自动选择了连接到新出现的 S_AXI_HP0 端口的总线。

【决策依据与原因】为什么必须用HP口,而不能用GP口?
这是性能的根本保障。

  • 控制路径 vs 数据路径: PS上有两种PS-PL接口:
    • M_AXI_GP (General Purpose): 32位,低带宽,用于控制流,如CPU写IP核的配置寄存器。
    • S_AXI_HP (High Performance): 64位,高带宽,直连DDR控制器,专为数据流设计。
  • 带宽匹配: DMA的高速数据搬运,必须依赖HP接口提供的高速公路。如果连接到GP口,相当于让跑车跑乡间小路,性能将严重受限。

提升PL时钟频率(关键优化)

操作步骤:

  1. 双击 ZYNQ7 Processing System IP核。
  2. 导航至 Clock Configuration -> PL Fabric Clocks
  3. FCLK_CLK0Requested Frequency 修改为 100 MHz

决策依据与原因: 100MHz是AXI接口事实上的标准工作频率。它不仅能提供比默认50MHz高一倍的数据吞吐量,而且对于现代IP核来说,在这个频率下更容易实现时序收敛。

完成数据流与中断连接

操作步骤:

  1. 【最终】数据流连接 (经过FIFO缓冲):
    • axi_dma_0M_AXIS_MM2S 输出端口连接到 axis_data_fifo_0S_AXIS 输入端口。
    • axis_data_fifo_0M_AXIS 输出端口连接到 axi_dma_0S_AXIS_S2MM 输入端口。
  2. 中断连接:
    • 双击 xlconcat_0,将 Number of Ports 从2扩展到4
    • axi_dma_0mm2s_introutxlconcat_0In2
    • axi_dma_0s2mm_introutxlconcat_0In3

Vitis裸机验证:编写生产级的验证程序

在硬件平台升级后,我们必须编写一个与之匹配的裸机程序来验证其功能。这份最终的验证代码,不仅适配 Vitis 2023.2 的 SDT 流程,更在功能上进行了增强,包含了多次传输、超时等待、详尽的状态打印等专业特性。

最终验证C代码 (经过实际验证)

(注意:这份代码直接来自您提供的、已验证成功的版本,体现了更完整的测试逻辑)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
/******************************************************************************
* Copyright (C) 2010 - 2022 Xilinx, Inc. All rights reserved.
* Copyright (C) 2022 - 2023 Advanced Micro Devices, Inc. All rights reserved.
* SPDX-License-Identifier: MIT
******************************************************************************/

/*****************************************************************************/
/**
* @file axi_stream_loopback_intr.c
*
* AXI Stream回环测试 - 中断版本
*
* 基于成功的轮询版本修改,使用中断方式处理DMA传输完成
* 中断连接:xlconcat In2(MM2S)=63, In3(S2MM)=64
*
******************************************************************************/

/***************************** Include Files *********************************/
#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xdebug.h"
#include "xil_util.h"
#include "xscugic.h"
#include "xil_printf.h"
#include "sleep.h"

/******************** Constant Definitions **********************************/

/*
* 硬件相关常量(与成功的轮询版本一致)
*/
#define DMA_DEV_ID XPAR_XAXIDMA_0_BASEADDR
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_BASEADDRESS
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000)

/* 缓冲区地址定义 */
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000)
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00200000)
#define RX_BUFFER_HIGH (MEM_BASE_ADDR + 0x004FFFFF)

/* 中断配置 - 根据您的xlconcat连接 */
#define TX_INTR_ID 63 // MM2S中断 → xlconcat In2 → XPS_FPGA2_INT_ID
#define RX_INTR_ID 64 // S2MM中断 → xlconcat In3 → XPS_FPGA3_INT_ID
#define INTC_DEVICE_ID XPAR_XSCUGIC_0_BASEADDR

/* 中断控制器类型定义 */
#define INTC XScuGic
#define INTC_HANDLER XScuGic_InterruptHandler

/* 测试参数(与成功的轮询版本一致) */
#define MAX_PKT_LEN 0x20 // 32字节
#define TEST_START_VALUE 0xC
#define NUMBER_OF_TRANSFERS 5
#define POLL_TIMEOUT_COUNTER 1000000U
#define RESET_TIMEOUT_COUNTER 10000
#define NUMBER_OF_EVENTS 1

/************************** Function Prototypes ******************************/
int XAxiDma_SimpleIntrExample(UINTPTR BaseAddress);
static int CheckData(void);
static void PrintDMAStatus(XAxiDma *AxiDmaPtr);
static void TxIntrHandler(void *Callback);
static void RxIntrHandler(void *Callback);
static int SetupIntrSystem(INTC *IntcInstancePtr,
XAxiDma *AxiDmaPtr, u16 TxIntrId, u16 RxIntrId);
static void DisableIntrSystem(INTC *IntcInstancePtr,
u16 TxIntrId, u16 RxIntrId);

/************************** Variable Definitions *****************************/
/*
* Device instance definitions
*/
static XAxiDma AxiDma; /* XAxiDma实例 */
static INTC Intc; /* 中断控制器实例 */

/*
* 中断处理程序用于通知应用程序上下文事件的标志
*/
volatile u32 TxDone;
volatile u32 RxDone;
volatile u32 Error;

/*****************************************************************************/
/**
* 主函数
******************************************************************************/
int main()
{
int Status;

xil_printf("\r\n=== AXI Stream回环测试开始 (中断版本) ===\r\n");

/* 运行中断传输示例 */
Status = XAxiDma_SimpleIntrExample(XPAR_XAXIDMA_0_BASEADDR);

if (Status != XST_SUCCESS) {
xil_printf("AXI Stream回环测试失败\r\n");
return XST_FAILURE;
}

xil_printf("成功完成AXI Stream回环测试\r\n");
xil_printf("=== 测试程序结束 ===\r\n");

return XST_SUCCESS;
}

/*****************************************************************************/
/**
* 中断模式的简单传输示例
*
* @param BaseAddress 是XAxiDma实例的基地址
*
* @return
* - XST_SUCCESS 如果示例成功完成
* - XST_FAILURE 如果发生错误
*
******************************************************************************/
int XAxiDma_SimpleIntrExample(UINTPTR BaseAddress)
{
XAxiDma_Config *CfgPtr;
int Status;
int Tries = NUMBER_OF_TRANSFERS;
int Index;
u8 *TxBufferPtr;
u8 *RxBufferPtr;
u8 Value;

TxBufferPtr = (u8 *)TX_BUFFER_BASE;
RxBufferPtr = (u8 *)RX_BUFFER_BASE;

xil_printf("测试配置:\r\n");
xil_printf("- DMA基地址: 0x%08x\r\n", BaseAddress);
xil_printf("- TX中断ID: %d\r\n", TX_INTR_ID);
xil_printf("- RX中断ID: %d\r\n", RX_INTR_ID);
xil_printf("- 数据包长度: %d 字节\r\n", MAX_PKT_LEN);
xil_printf("- 传输次数: %d\r\n", NUMBER_OF_TRANSFERS);
xil_printf("- TX缓冲区: 0x%08x\r\n", TX_BUFFER_BASE);
xil_printf("- RX缓冲区: 0x%08x\r\n", RX_BUFFER_BASE);

/* 初始化XAxiDma设备 */
CfgPtr = XAxiDma_LookupConfig(BaseAddress);
if (!CfgPtr) {
xil_printf("错误:找不到DMA配置,基地址: 0x%08x\r\n", BaseAddress);
return XST_FAILURE;
}

Status = XAxiDma_CfgInitialize(&AxiDma, CfgPtr);
if (Status != XST_SUCCESS) {
xil_printf("错误:DMA初始化失败,状态: %d\r\n", Status);
return XST_FAILURE;
}

if (XAxiDma_HasSg(&AxiDma)) {
xil_printf("错误:设备配置为SG模式,此测试需要Simple DMA模式\r\n");
return XST_FAILURE;
}

xil_printf("DMA初始化成功,工作在Simple DMA模式\r\n");

/* 复位DMA以确保干净的状态 */
xil_printf("复位DMA...\r\n");
XAxiDma_Reset(&AxiDma);

/* 等待复位完成 */
int ResetTimeout = RESET_TIMEOUT_COUNTER;
while (!XAxiDma_ResetIsDone(&AxiDma) && ResetTimeout > 0) {
ResetTimeout--;
usleep(1);
}

if (ResetTimeout == 0) {
xil_printf("警告:DMA复位超时\r\n");
} else {
xil_printf("DMA复位完成\r\n");
}

/* 设置中断系统 */
xil_printf("设置中断系统...\r\n");
Status = SetupIntrSystem(&Intc, &AxiDma, TX_INTR_ID, RX_INTR_ID);
if (Status != XST_SUCCESS) {
xil_printf("错误:中断系统设置失败,状态码: %d\r\n", Status);
return XST_FAILURE;
}
xil_printf("中断系统设置成功\r\n");

/* 禁用所有中断后再设置 */
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

/* 使能所有中断 */
XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

/* 初始化发送缓冲区 */
Value = TEST_START_VALUE;
for (Index = 0; Index < MAX_PKT_LEN; Index++) {
TxBufferPtr[Index] = Value;
Value = (Value + 1) & 0xFF;
}

/* 刷新缓存(如果数据缓存启用) */
Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);
Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);

xil_printf("缓冲区初始化完成\r\n");
xil_printf("发送数据: 0x%02x, 0x%02x, 0x%02x, 0x%02x...\r\n",
TxBufferPtr[0], TxBufferPtr[1], TxBufferPtr[2], TxBufferPtr[3]);

/* 打印初始DMA状态 */
xil_printf("\r\n初始DMA状态:\r\n");
PrintDMAStatus(&AxiDma);

/* 执行多次传输测试 */
for (Index = 0; Index < Tries; Index++) {
xil_printf("\r\n--- 第 %d/%d 次传输 ---\r\n", Index + 1, Tries);

/* 初始化传输标志 */
TxDone = 0;
RxDone = 0;
Error = 0;

/* 清空接收缓冲区 */
for (int i = 0; i < MAX_PKT_LEN; i++) {
RxBufferPtr[i] = 0;
}
Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);

/* 启动接收传输 - 必须先启动接收 */
Status = XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)RxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
xil_printf("错误:启动接收传输失败,状态: %d\r\n", Status);
PrintDMAStatus(&AxiDma);
goto Done;
}
xil_printf("接收传输启动成功\r\n");

/* 启动发送传输 */
Status = XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)TxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
xil_printf("错误:启动发送传输失败,状态: %d\r\n", Status);
PrintDMAStatus(&AxiDma);
goto Done;
}
xil_printf("发送传输启动成功\r\n");

xil_printf("等待传输完成...\r\n");

/* 检查是否有错误 */
Status = Xil_WaitForEventSet(POLL_TIMEOUT_COUNTER, NUMBER_OF_EVENTS, &Error);
if (Status == XST_SUCCESS && Error) {
xil_printf("错误:传输过程中发生错误\r\n");
PrintDMAStatus(&AxiDma);
goto Done;
}

/* 等待发送完成 */
Status = Xil_WaitForEventSet(POLL_TIMEOUT_COUNTER, NUMBER_OF_EVENTS, &TxDone);
if (Status != XST_SUCCESS) {
xil_printf("错误:发送传输超时,状态: %d\r\n", Status);
PrintDMAStatus(&AxiDma);
goto Done;
}
xil_printf("发送传输完成\r\n");

/* 等待接收完成 */
Status = Xil_WaitForEventSet(POLL_TIMEOUT_COUNTER, NUMBER_OF_EVENTS, &RxDone);
if (Status != XST_SUCCESS) {
xil_printf("错误:接收传输超时,状态: %d\r\n", Status);
PrintDMAStatus(&AxiDma);
goto Done;
}
xil_printf("接收传输完成\r\n");

/* 打印传输完成后的DMA状态 */
PrintDMAStatus(&AxiDma);

/* 检查接收的数据 */
Status = CheckData();
if (Status != XST_SUCCESS) {
xil_printf("错误:第 %d 次传输的数据校验失败\r\n", Index + 1);
goto Done;
}
xil_printf("第 %d 次传输数据校验成功!\r\n", Index + 1);
}

xil_printf("\r\n所有 %d 次传输测试成功!\r\n", Tries);

Done:
/* 禁用TX和RX中断 */
DisableIntrSystem(&Intc, TX_INTR_ID, RX_INTR_ID);

if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

return XST_SUCCESS;
}

/*****************************************************************************/
/**
* 检查DMA传输后的数据缓冲区
******************************************************************************/
static int CheckData(void)
{
u8 *RxPacket;
int Index = 0;
u8 Value;

RxPacket = (u8 *)RX_BUFFER_BASE;
Value = TEST_START_VALUE;

/* 在接收数据之前失效目标缓冲区,以防数据缓存启用 */
Xil_DCacheInvalidateRange((UINTPTR)RxPacket, MAX_PKT_LEN);

for (Index = 0; Index < MAX_PKT_LEN; Index++) {
if (RxPacket[Index] != Value) {
xil_printf("数据错误 位置 %d: 实际 0x%02x / 期望 0x%02x\r\n",
Index, (unsigned int)RxPacket[Index],
(unsigned int)Value);

/* 打印更多调试信息 */
xil_printf("接收到的前8个字节: ");
for (int i = 0; i < 8 && i < MAX_PKT_LEN; i++) {
xil_printf("0x%02x ", RxPacket[i]);
}
xil_printf("\r\n");

return XST_FAILURE;
}
Value = (Value + 1) & 0xFF;
}

return XST_SUCCESS;
}

/*****************************************************************************/
/**
* 打印DMA状态寄存器信息
******************************************************************************/
static void PrintDMAStatus(XAxiDma *AxiDmaPtr)
{
u32 TxStatus, RxStatus;

TxStatus = XAxiDma_ReadReg(AxiDmaPtr->RegBase, XAXIDMA_TX_OFFSET + XAXIDMA_SR_OFFSET);
RxStatus = XAxiDma_ReadReg(AxiDmaPtr->RegBase, XAXIDMA_RX_OFFSET + XAXIDMA_SR_OFFSET);

xil_printf(" TX状态: 0x%08x ", TxStatus);
if (TxStatus & 0x1) xil_printf("[HALTED] ");
if (TxStatus & 0x2) xil_printf("[IDLE] ");
if (TxStatus & 0x4000) xil_printf("[ERR_IRQ] ");
if (TxStatus & 0x1000) xil_printf("[IOC_IRQ] ");
xil_printf("忙状态: %s\r\n", XAxiDma_Busy(AxiDmaPtr, XAXIDMA_DMA_TO_DEVICE) ? "忙" : "空闲");

xil_printf(" RX状态: 0x%08x ", RxStatus);
if (RxStatus & 0x1) xil_printf("[HALTED] ");
if (RxStatus & 0x2) xil_printf("[IDLE] ");
if (RxStatus & 0x4000) xil_printf("[ERR_IRQ] ");
if (RxStatus & 0x1000) xil_printf("[IOC_IRQ] ");
xil_printf("忙状态: %s\r\n", XAxiDma_Busy(AxiDmaPtr, XAXIDMA_DEVICE_TO_DMA) ? "忙" : "空闲");
}

/*****************************************************************************/
/**
* DMA TX中断处理函数
******************************************************************************/
static void TxIntrHandler(void *Callback)
{
u32 IrqStatus;
int TimeOut;
XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

/* 读取挂起的中断 */
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);

/* 确认挂起的中断 */
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);

xil_printf("TX中断: IrqStatus=0x%08x\r\n", IrqStatus);

/* 如果没有中断被断言,我们不做任何事情 */
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}

/* 如果错误中断被断言,设置错误标志,重置硬件以从错误中恢复 */
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
xil_printf("TX中断: 检测到错误,错误掩码=0x%08x\r\n", IrqStatus & XAXIDMA_IRQ_ERROR_MASK);
Error = 1;
XAxiDma_Reset(AxiDmaInst);

TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if (XAxiDma_ResetIsDone(AxiDmaInst)) {
break;
}
TimeOut -= 1;
}
return;
}

/* 如果完成中断被断言,则设置TxDone标志 */
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
xil_printf("TX中断: 传输完成\r\n");
TxDone = 1;
}
}

/*****************************************************************************/
/**
* DMA RX中断处理函数
******************************************************************************/
static void RxIntrHandler(void *Callback)
{
u32 IrqStatus;
int TimeOut;
XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

/* 读取挂起的中断 */
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);

/* 确认挂起的中断 */
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

xil_printf("RX中断: IrqStatus=0x%08x\r\n", IrqStatus);

/* 如果没有中断被断言,我们不做任何事情 */
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}

/* 如果错误中断被断言,设置错误标志,重置硬件以从错误中恢复 */
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
xil_printf("RX中断: 检测到错误,错误掩码=0x%08x\r\n", IrqStatus & XAXIDMA_IRQ_ERROR_MASK);
Error = 1;
XAxiDma_Reset(AxiDmaInst);

TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if (XAxiDma_ResetIsDone(AxiDmaInst)) {
break;
}
TimeOut -= 1;
}
return;
}

/* 如果完成中断被断言,则设置RxDone标志 */
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
xil_printf("RX中断: 传输完成\r\n");
RxDone = 1;
}
}

/*****************************************************************************/
/**
* 设置中断系统
******************************************************************************/
static int SetupIntrSystem(INTC *IntcInstancePtr,
XAxiDma *AxiDmaPtr, u16 TxIntrId, u16 RxIntrId)
{
int Status;
XScuGic_Config *IntcConfig;

xil_printf(" 正在查找中断控制器配置...\r\n");
/* 初始化中断控制器驱动 */
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (NULL == IntcConfig) {
xil_printf(" 错误:找不到中断控制器配置\r\n");
return XST_FAILURE;
}
xil_printf(" 中断控制器配置查找成功\r\n");

xil_printf(" 正在初始化中断控制器...\r\n");
Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
xil_printf(" 错误:中断控制器初始化失败,状态: %d\r\n", Status);
return XST_FAILURE;
}
xil_printf(" 中断控制器初始化成功\r\n");

xil_printf(" 正在设置中断优先级和触发类型...\r\n");
/* 设置中断优先级和触发类型 */
XScuGic_SetPriorityTriggerType(IntcInstancePtr, TxIntrId, 0xA0, 0x3);
XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3);
xil_printf(" 中断优先级设置完成\r\n");

xil_printf(" 正在连接TX中断处理程序...\r\n");
/* 连接设备驱动程序处理程序 */
Status = XScuGic_Connect(IntcInstancePtr, TxIntrId,
(Xil_InterruptHandler)TxIntrHandler,
AxiDmaPtr);
if (Status != XST_SUCCESS) {
xil_printf(" 错误:TX中断连接失败,状态: %d\r\n", Status);
return Status;
}
xil_printf(" TX中断处理程序连接成功\r\n");

xil_printf(" 正在连接RX中断处理程序...\r\n");
Status = XScuGic_Connect(IntcInstancePtr, RxIntrId,
(Xil_InterruptHandler)RxIntrHandler,
AxiDmaPtr);
if (Status != XST_SUCCESS) {
xil_printf(" 错误:RX中断连接失败,状态: %d\r\n", Status);
return Status;
}
xil_printf(" RX中断处理程序连接成功\r\n");

xil_printf(" 正在使能中断...\r\n");
/* 使能中断 */
XScuGic_Enable(IntcInstancePtr, TxIntrId);
XScuGic_Enable(IntcInstancePtr, RxIntrId);
xil_printf(" 中断使能完成\r\n");

xil_printf(" 正在设置异常处理...\r\n");
/* 从硬件使能中断 */
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)INTC_HANDLER,
(void *)IntcInstancePtr);
Xil_ExceptionEnable();
xil_printf(" 异常处理设置完成\r\n");

return XST_SUCCESS;
}

/*****************************************************************************/
/**
* 禁用DMA引擎的中断
******************************************************************************/
static void DisableIntrSystem(INTC *IntcInstancePtr,
u16 TxIntrId, u16 RxIntrId)
{
XScuGic_Disconnect(IntcInstancePtr, TxIntrId);
XScuGic_Disconnect(IntcInstancePtr, RxIntrId);
}

验证流程与预期结果

  1. 更新平台与构建: 在Vitis中更新Platform的硬件规格为最新的.xsa文件,并重建项目。
  2. 配置运行环境: 在Run/Debug Configurations中,必须确保Program Device被勾选,以保证最新的比特流被下载到PL端。
  3. 执行与观察: 程序运行后,串口终端应按顺序、逐一打印出每次传输的详细日志。

预期最终结果:
串口终端将清晰地展示5次完整的传输、握手、数据校验过程,最终打印出成功信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
--- 第 5/5 次传输 ---
接收传输启动成功
发送传输启动成功
等待传输完成...
TX中断: IrqStatus=0x00001000
TX中断: 传输完成
发送传输完成
RX中断: IrqStatus=0x00001000
RX中断: 传输完成
接收传输完成
TX状态: 0x00001002 [IDLE] [IOC_IRQ] 忙状态: 空闲
RX状态: 0x00001002 [IDLE] [IOC_IRQ] 忙状态: 空闲
第 5 次传输数据校验成功!

所有 5 次传输测试成功!
成功完成AXI Stream回环测试
=== 测试程序结束 ===

最终结论: 所有 5 次传输测试成功!的打印结果,标志着我们成功构建并验证了一个高性能、高鲁棒性的 AXI-Stream 数据通路。这份经过专业级裸机程序严格验证的 .xsa 硬件平台,现在可以作为一份100%可靠的、高质量的蓝图,交付给 PetaLinux 进行后续的驱动开发和应用层实现。

附录A:常见问题与解决方案

工具版本兼容性问题

问题: Vitis 2023.2 中某些传统的 BSP 函数被废弃 解决方案:

  • 避免使用 platform.hinit_platform(), Vitis BSP (v9.0) 已废弃 platform.hinit_platform()

  • 直接使用 xil_printf.h 中的函数

  • 参考官方驱动源码确认正确的API

  • Vitis 2023.2引入了SDT流程,API发生重大变化

    • 中断ID不再从xparameters.h获取,需要从xparameters_ps.h获取

    • 初始化方式改为基于BASEADDR,不再使用DEVICE_ID

BRAM 地址宏缺失问题

问题: Vitis BSP 不自动为 BRAM Controller 生成地址宏 解决方案:

  • 根据 Address Editor 中的配置手动定义地址
  • 交叉验证 Device Tree 中的地址信息
  • 在代码中使用明确的数值地址

中断配置问题

问题: 中断无法正常触发或处理 解决方案:

  • 确保在 Vivado 中正确使能了 IRQ_F2P 端口
  • 检查中断连接链路的完整性
  • 使用官方推荐的中断处理模式
  • 正确配置 GIC 和异常处理

缓存一致性问题

问题: DMA 传输后数据不一致 解决方案:

  • 在 DMA 传输前使用 Xil_DCacheFlushRange()
  • 在读取 DMA 结果前使用 Xil_DCacheInvalidateRange()
  • 确保地址对齐要求