ZYNQ笔记(十三):双核 AMP 通信实验
ZYNQ学习笔记
版本:Vivado2020.2(Vitis)
ZYNQ 裸机双核 AMP 实验:
CPU0 接收串口的数据,并写入 OCM 中,然后利用软件产生中断触发 CPU1;CPU1 接收到中断后,根据从 OCM 中读出的数据控制呼吸灯的频率,并在控制结束后触发 CPU0 中断,实现了双核 CPU 通信的功能。
目录
一、介绍
Zynq-7000 SoC 集成了 双核 ARM Cortex-A9 MPCore,采用对称多处理(SMP)架构,同时支持非对称多处理(AMP)模式,适用于嵌入式高性能计算和实时控制场景。

(1)双核 AMP 通信
直接附上正点原子开发指南的内容切片:

— — — — — — — — — — — — — 分割线 — — — — — — — — — — — —

(2)ZYNQ启动流程(裸机)
上面介绍有提到了一些专有名词(如BootROM、FSBL、OCM),这里连带介绍一下 ZYQN(Xilinx Zynq-7000 SoC) 裸机开发时的启动流程,主要分为以下几个阶段:
1. Boot ROM
-
硬件自动执行:上电或复位后,芯片首先运行片内ROM中的固化代码(Boot ROM)。
-
启动模式检测:根据硬件配置(如模式引脚设置)确定启动源(如QSPI Flash、SD卡、NAND Flash、JTAG等)。
-
加载FSBL:从启动设备中读取 FSBL(First Stage Boot Loader)到芯片的 OCM(On-Chip Memory)或 DDR 中(需预先配置DDR,本次例程配置了所以程序基于 DDR 运行)。
2. FSBL(First Stage Boot Loader)
-
功能:
-
初始化必要外设(如DDR控制器、时钟、PLL)。
-
从Flash或SD卡中加载用户程序(裸机代码或第二阶段引导程序)。
-
可配置 PL(Programmable Logic)部分(可选,需加载比特流文件)。
-
-
生成方式:通过 Xilinx SDK 或 Vitis 工具链生成(基于用户硬件设计)。
-
执行位置:通常在 OCM 中运行。
3. 用户程序(裸机代码)
-
加载与执行:FSBL将用户编写的裸机程序(ELF文件)加载到DDR或OCM中,并跳转到其入口地址。执行程序。
二、硬件设计
(1)本实验是用软件产生中断(SGI)的方式来控制两个 CPU 访问共享内存的过程:
首先需要处理器向共享内存中写入一个数据,本实验选择 CPU0 往共享内存中写入数据。当 CPU0 写入成功后就发送一条软中断指令去触发 CPU1 去读取共享内存中的数据。
当 CPU1 读取到共享内存的数据后需要通过 M_AXI_GP0 接口的 AXI4-Lite 总线将数据发送到 PL 端呼吸灯的 IP 核, 呼吸灯的IP核根据PS发过来的数据对呼吸频率进行相应的配置,然后 CPU1 发送一条软中断指令触发 CPU0 继续往共享内存中写入数据。
到此便实现了两个 CPU 通过共享内存进行数据交互的功能,同时为了对IP核进行控制以及方便观察数据流的走向,用 UART 接收控制命令以及打印 Debug 信息。

(2)关于自定义 LED 呼吸灯 IP 核的设计与使用这里不再赘述,跟之前笔记的例程操作步骤一致,详细参考:ZYNQ笔记(六):自定义IP核-LED呼吸灯
(3)最后整体 bd 设计部分如图所示:设计检查、Generate Output Products、 Create HDL Wrapper、管脚约束、Gnerate Bitstream、Export Hardware(包含比特流文件)、启动Vitis

注意 LED 呼吸灯的 IP 核要手动添加 LED 管脚并与开发板的 LED 对应管脚绑定。
三、软件设计
(1)新建 CPU0 应用工程
新建工程名 “cpu0_uart” ,系统工程名“AMP_2core_system”,通过cpu0实现串口通信,主要是从uart接收控制命令。如图所示,同时处理器选择ps7_cortexa9_0(默认也是cpu0).
![]()
(2)分配 CPU0 DDR 空间
单核应用工程,默认将DDR空间全部分配给cpu0,这里用到了双核所以需要进行分配,就直接将DDR分二部分,一部分给cpu0,另一部分给cpu1(具体怎么大小分配都行),如图所示:

红框标注的地方左侧为基地址,右侧为存储空间大小(单位字节),存储空间大小将默认的完整空间大小 0x1FF00000 (十进制 535,822,336;535,822,336/1024/1024 = 511MByte),修改为为 0x0FF00000(十进制 267,386,880;267,386,880/1024/1024 = 255MByte)。修改完成后,按下“Ctrl”+ “S”保存。
另外图中的 ps7_ram_0 和 ps_ram_1 为 OCM 共享内存的基地址和存储空间大小。后面双核通信就是通过这访问这两块共享存储区域实现的,后面软件设计也会用到其的基地址以进行访问(本例程任选一个即可,后面的设计选用的是 ps_ram_0 基地址0x0)。
(3)新建 CPU1 应用工程
在创建 CPU1 应用工程之前,需要新建基于 CPU1 的板级支持包(BSP)。双击 platform.spr,打开 system_wrapper 设置界面,如下图所示:

之后依次对硬件平台工程(system_wrapper)右击选择 Clean Project 和 Build Project。
接下来创建 CPU1 的应用工程,右键系统工程 AMP_2core_system 选择“Add Application Project”,从而在系统工程下添加一个新的应用工程,工程名命名为 cpu1_led,处理器选择 ps7_cortexa9_1, 如图所示:

(4)分配 CPU0 DDR 空间
同样需要为 cpu2 分配 DDR 空间,两个核的DDR空间不能有重合,负责程序运行时会出现同时访问导致程序异常,所以这里要对起始地址和大小同时进行修改,如图所示:

起始地址 = CPU0 的 DDR 起始地址 + 其分配大小 = 0x10000 + 0x0FF00000 = 0x10000000 ;分配大小 ≤ DDR 完整大小 - CPU0 的 DDR 分配大小 = 0x1FF00000(由开发板DDR大小决定)- 0x0FF00000 = 0x10000000 。修改完成后,按下“Ctrl”+“S”保存。
(5)代码部分
新建 cpu0_uart 应用工程源文件“mian_0.c”,编写代码:
#include "xparameters.h"
#include "xscugic.h"
#include "xil_printf.h"
#include "xil_exception.h"
#include "xil_mmu.h"
#include "stdio.h"
//============================宏定义===============================//
#define SHARE_BASE 0x0 //共享OCM首地址(根据lscript.ld设置)
#define CPU1_COPY_ADDR 0xfffffff0 //存放CPU1应用起始地址的地址(参考ug585手册)
#define CPU1_START_ADDR 0x10000000 //CPU1应用起始地址(给cpu1分配的DDR起始地址)
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //GIC中断控制器ID
#define CPU1_ID XSCUGIC_SPI_CPU1_MASK //CPU1 ID
#define SOFT_INTR_ID_CPU0 0 //软件中断号 0 ,范围0~15
#define SOFT_INTR_ID_CPU1 1 //软件中断号 1 ,范围0~15
//"SEV"指令唤醒CPU1并跳转至相应的程序
#define sev() __asm__("sev") //C语言内嵌汇编写法 send event指令
//===========================函数声明==============================//
void start_cpu1();
void cpu0_intr_init(XScuGic *intc_ptr);
void soft_intr_handler(void *CallbackRef);
//===========================全局变量==============================//
XScuGic Intc; //中断控制器驱动程序实例
int rec_freq_flag = 0; //接收到呼吸灯频率设置的标志
int speed; //频率速度档位(0~7)
//=========================CPU0 main函数===========================//
int main()
{
//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(SHARE_BASE,0x14de2); //禁用OCM的Cache属性
//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(CPU1_COPY_ADDR,0x14de2);//禁用0xfffffff0的Cache属性
//启动CPU1(固化程序时需使用)
//start_cpu1();
//CPU0中断初始化
cpu0_intr_init(&Intc);
while(1){
if(rec_freq_flag == 0){
xil_printf("CPU0: Input 0~7 to change breath LED frequency \r\n");
xil_printf("\r\n");
//输入0~7指令
scanf("%d",&speed);
if(speed >= 0 && speed <= 7){
xil_printf("CPU0: Input Command %d \r\n",speed);
//向共享的地址中写入输入的数据
Xil_Out8(SHARE_BASE,speed);
//CPU0软件触发CPU1中断
XScuGic_SoftwareIntr(&Intc, SOFT_INTR_ID_CPU1, CPU1_ID);
rec_freq_flag = 1;
}
else{
xil_printf("CPU0: Command Error, out of range 0~7 \r\n");
}
}
}
return 0 ;
}
//====================启动CPU1,用于固化程序=======================//
// 如果只是通过 JTAG 模式下载程序,可以不编写这个启动函数,这个函数仅在固化程序的时候起作用。
void start_cpu1()
{
//向 CPU1_COPY_ADDR(0Xffffffff0)地址写入 CPU1 的访问内存基地址
Xil_Out32(CPU1_COPY_ADDR, CPU1_START_ADDR);
dmb(); //等待内存写入完成(同步)
sev(); //通过"SEV"指令唤醒CPU1并跳转至相应的程序
}
//=========================中断处理函数===========================//
void soft_intr_handler(void *CallbackRef)
{
xil_printf("CPU0: Received Soft Interrupt from CPU1 (set finish) \r\n");
rec_freq_flag = 0;
}
//========================CPU0中断初始化==========================//
void cpu0_intr_init(XScuGic *intc_ptr)
{
//初始化中断控制器
XScuGic_Config *intc_cfg_ptr;
intc_cfg_ptr = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(intc_ptr, intc_cfg_ptr,
intc_cfg_ptr->CpuBaseAddress);
//设置中断异常处理功能
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_ptr);
//使能处理器中断
Xil_ExceptionEnable();
//关联中断函数
XScuGic_Connect(intc_ptr, SOFT_INTR_ID_CPU0,
(Xil_ExceptionHandler)soft_intr_handler, (void *)intc_ptr);
//使能中断
XScuGic_Enable(intc_ptr, SOFT_INTR_ID_CPU0); //CPU0软件中断
}
新建 cpu1_led 应用工程源文件“mian_1.c”,编写代码:
#include "xparameters.h"
#include "xscugic.h"
#include "xil_printf.h"
#include "xil_exception.h"
#include "xil_mmu.h"
#include "stdio.h"
#include "breath_led_ip.h"
//============================宏定义===============================//
#define SHARE_BASE 0x0 //共享OCM首地址(根据lscript.ld设置)
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //GIC中断控制器ID
#define CPU0_ID XSCUGIC_SPI_CPU0_MASK //CPU0 ID
#define SOFT_INTR_ID_CPU0 0 //软件中断号 0 ,范围0~15
#define SOFT_INTR_ID_CPU1 1 //软件中断号 1 ,范围0~15
#define LED_IP_BASEADDR XPAR_BREATH_LED_IP_0_S0_AXI_BASEADDR //LED IP基地址
#define LED_EN BREATH_LED_IP_S0_AXI_SLV_REG0_OFFSET //宏定义led_en 对应寄存器(slv_reg0)地址偏移量
#define SPEED_EN BREATH_LED_IP_S0_AXI_SLV_REG1_OFFSET //宏定义speed_en对应寄存器(slv_reg1)地址偏移量
#define SPEED BREATH_LED_IP_S0_AXI_SLV_REG2_OFFSET //宏定义speed 对应寄存器(slv_reg2)地址偏移量
//===========================函数声明==============================//
void cpu1_intr_init(XScuGic *intc_ptr);
void soft_intr_handler(void *CallbackRef);
//===========================全局变量==============================//
XScuGic Intc; //中断控制器驱动程序实例
int soft_intr_flag = 0; //软件中断的标志
int speed; //频率档位(0~7)
//=========================CPU1 main函数===========================//
int main()
{
//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(SHARE_BASE,0x14de2); //禁用OCM的Cache属性
//CPU1中断初始化
cpu1_intr_init(&Intc);
//打开LED呼吸灯
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR, LED_EN, 0x1);
//使能LED呼吸灯频率设置
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR, SPEED_EN, 0x1);
while(1){
if(soft_intr_flag){
speed = Xil_In8(SHARE_BASE); //从共享OCM中读出数据
xil_printf("CUP1: Received Command is %d \r\n",speed) ;
//设置LED呼吸灯频率
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR, SPEED, speed);
//CPU1软件触发CPU0中断
XScuGic_SoftwareIntr(&Intc,SOFT_INTR_ID_CPU0,CPU0_ID);
soft_intr_flag = 0;
}
}
return 0 ;
}
//=========================中断处理函数===========================//
void soft_intr_handler(void *CallbackRef)
{
xil_printf("CPU1: Received Soft Interrupt from CPU0 (command ready) \r\n") ;
soft_intr_flag = 1;
}
//=========================CPU1中断初始化=========================//
void cpu1_intr_init(XScuGic *intc_ptr)
{
//初始化中断控制器
XScuGic_Config *intc_cfg_ptr;
intc_cfg_ptr = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(intc_ptr, intc_cfg_ptr,
intc_cfg_ptr->CpuBaseAddress);
//设置中断异常处理功能
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_ptr);
//使能处理器中断
Xil_ExceptionEnable();
//关联中断函数
XScuGic_Connect(intc_ptr, SOFT_INTR_ID_CPU1,
(Xil_ExceptionHandler)soft_intr_handler, (void *)intc_ptr);
//使能中断
XScuGic_Enable(intc_ptr, SOFT_INTR_ID_CPU1); //CPU1软件中断
}
(6)下载
下载时需要将两个应用工程以及FPGA的比特流文件全部勾选下载。操作如图所示:

选中两个核的应用工程:

选中同时下载FPGA波特率文件(这个选项一般是默认勾选了):
四、效果
1.下载后CPU0打印输入指令的提示信息,同时LED灯已经开始工作:
2.第一次输入9(正确指令范围0~7),CPU0打印报错信息并重新提示输入指令。
3.第二次输入1,CPU0打印出接受到的正确指令,接着CPU1打印接收到来自CPU0的中断的提示(表示CPU0已经将指令写入OCM),接着CPU0打印接收到来自CPU1的中断的提示(表示CPU1已经从OCM读取指令并设置LED灯速率),此时LED呼吸灯的速度发生对应变化(比初始的0档位闪烁的快一点),最后CPU0打印输入指令的提示信息(表示可以进行下一次设置操作)
4.第三次输入7,流程同上,LED灯速率达到最大,闪烁最快。

注意:
本次例程用到scanf()(头文件 stidio.h)实现串口输入指令,这种方式可以在vitis串口终端上进行调试。但是如果要用一般的串口助手调试就不行,需要修改串口部分的软件设计,可以加上串口中断功能实现接收串口输入输出。参考笔记:ZYNQ笔记(八):UART 串口中断
本次例程没有进行程序固化,所以没有使用到CPU0启动CPU1的函数,如果需要固化需要实现该函数,同时也许在硬件设计作配置,例如固化程序到 Flash 需要配置 QSPI Flash ,相关固化可操作参考笔记:ZYNQ笔记(七):程序固化(QSPI Flash)
更多推荐




所有评论(0)