Vivado 18.3 + PYNQ-Z2实战:ZYNQ双核AMP通信全流程解析与深度优化
第一次在PYNQ-Z2上尝试双核AMP通信时,我遇到了一个令人抓狂的问题:明明按照教程配置了OCM共享内存,两个核心却总是无法正常交换数据。经过三天三夜的调试才发现,问题出在缓存一致性上——这个在单核编程中几乎不会遇到的"隐形杀手"。本文将带你从零开始,避开那些教科书上不会告诉你的坑,实现稳定可靠的双核通信。
1. 硬件平台搭建:从零开始的正确姿势
1.1 Vivado工程创建关键配置
新建Vivado工程时,选择PYNQ-Z2对应的器件型号xc7z020clg400-1。创建Block Design后,添加ZYNQ7 Processing System IP核,这些基础操作看似简单,但有几个关键配置点直接影响后续双核通信:
- 时钟配置:确保CPU时钟频率一致(建议默认666MHz)
- DDR控制器:保持默认配置,但注意地址范围
- OCM配置:保留64KB OCM空间(地址范围0xFFFF0000-0xFFFFFFFF)
特别注意:不要勾选"Enable Clock Resets"选项,这会导致CPU1无法正常启动
1.2 中断控制器配置秘籍
在ZYNQ IP配置界面,找到PS-PL Configuration → General → Enable Interrupts,确保勾选了:
- Software Generated Interrupts(必须启用)
- CPU1 Software Interrupt(默认禁用,需手动启用)
# 生成硬件设计后必须执行的Tcl命令 validate_bd_design generate_target all [get_files *.bd]2. SDK工程双核配置实战
2.1 双核工程创建与地址空间划分
在Vivado导出硬件后启动SDK,创建两个独立工程时需特别注意:
CPU0工程:
- 处理器选择ps7_cortexa9_0
- 修改lscript.ld链接脚本:
/* 原始DDR配置 */ DDR_ORIGIN = 0x100000 DDR_LENGTH = 0x1FF00000 /* 修改为 */ DDR_ORIGIN = 0x100000 DDR_LENGTH = 0xFF00000 // 只使用前一半空间
CPU1工程:
- 处理器选择ps7_cortexa9_1
- DDR配置:
DDR_ORIGIN = 0x10000000 // CPU0结束地址 DDR_LENGTH = 0xFF00000 // 使用后一半空间
2.2 容易被忽略的编译器设置
两个工程都需要在Board Support Package Settings中添加AMP宏定义:
extra_compiler_flags = -DUSE_AMP=13. OCM共享内存的陷阱与解决方案
3.1 缓存一致性问题深度解析
ZYNQ的OCM内存默认启用缓存,这会导致双核看到的数据不一致。必须通过MMU修改内存属性:
// 禁用OCM区域缓存(关键!) Xil_SetTlbAttributes(0xFFFF0000, 0x14de2); // 参数解析:S=1, TEX=100, AP=11, Domain=1111, C=0, B=03.2 双核同步机制设计
推荐使用软件中断+内存标志位的混合同步方案:
- 中断配置表:
| 中断号 | 触发核心 | 目标核心 | 用途 |
|---|---|---|---|
| 0 | CPU1 | CPU0 | 数据就绪通知 |
| 1 | CPU0 | CPU1 | 数据处理完成 |
- 典型通信流程:
- CPU0写入数据到OCM
- 触发CPU1中断(编号1)
- CPU1读取数据并处理
- 触发CPU0中断(编号0)确认
4. 双核程序固化与启动优化
4.1 多核固件合成技巧
创建FSBL时,需要特别注意BOOT.BIN的组成结构:
标准组成:
- FSBL.elf
- system.bit
- u-boot.elf
AMP模式新增:
- cpu0_app.elf
- cpu1_app.elf
# 使用bootgen生成BOOT.BIN的示例命令 bootgen -image boot.bif -arch zynq -o BOOT.BIN -w4.2 启动顺序控制
在CPU0的main函数开始处添加CPU1唤醒代码:
#define sev() __asm__("sev") // ARM SEV指令 void start_cpu1() { // 0xFFFFFFF0是CPU1启动地址的存储位置 Xil_Out32(0xFFFFFFF0, 0x10000000); dmb(); // 内存屏障确保写入完成 sev(); // 唤醒CPU1 }5. 高级调试技巧与性能优化
5.1 双核调试配置
在SDK中同时调试双核需要特殊配置:
调试配置步骤:
- 先连接CPU0,暂停执行
- 再连接CPU1,设置断点
- 同时恢复两个核心执行
常用调试命令:
# 查看CPU0寄存器 arm-none-eabi-readelf -a cpu0.elf # 查看内存映射 mrd 0xFFFF0000 10
5.2 性能优化策略
通过实测发现以下优化可提升通信效率30%以上:
内存布局优化:
// 将频繁通信的数据放在OCM高地址段 #define SHARED_DATA_BASE 0xFFFFF000中断延迟优化:
// 设置GIC优先级 XScuGic_SetPriorityTriggerType(&Intc, int_id, priority, trigger);
在实际项目中,我遇到最棘手的问题是CPU1偶尔无法正常启动。最终发现是FSBL没有正确识别多核固件,通过在bootgen配置中明确指定CPU1的加载地址解决了这个问题。建议大家在第一次烧录后,先用JTAG验证双核是否都进入了main函数。