一、简介
多核处理器从多核的结构上是否一致,分为两种基本架构:同构多核架构和异构多核架构。同构多核
处理器是指系统中的处理器在结构上是相同的;而异构处理器是指系统中的处理器在结构上是不同的,这
些处理器可以是通用处理器,也可以是解决某些特定应用的专用硬核。同构多核架构相比于异构多核架构,在硬件和软件设计上较为简单,通用性较高。但在某些特定应用场合下,如异构多核架构专用的硬件加速硬核,异构多核架构的性能会更高。
Xilinx 的 ZYNQ SOC 融合了这两种架构,ZYNQ SOC 芯片包含两个独立的 Cortex-A9 处理器,这两个
处理器核在结构上是相同的,同时又包括了可编程的逻辑单元(PL),使得 ZYNQ 整体系统成为了一个异构多核系统,同时具有较高的通用性和性能。
从软件的角度看,多核处理器的运行模式有 AMP(非对称多处理)、SMP(对称多处理)和 BMP(受
约束多处理)三种运行模式。
AMP 运行模式指多个内核相对独立的运行不同的任务,每个内核相互隔离,可以运行不同的操作系统
(OS)或裸机应用程序。
SMP 运行模式指多个处理器运行一个操作系统,这个操作系统同等的管理多个内核,如 PC 电脑。
BMP 运行模式与 SMP 类似,但开发者可以指定将某个任务仅在某个指定内核上执行。
一般来说,SMP 为较高级的应用提供统一的 OS 平台,开发者在 OS 之上构建应用时,无需考虑两个内
核之间的资源共享和进程间通信。除此之外,对 SMP 而言存在性能开销,这会对实时性要求较高的应用,其性能造成较大影响。如 PC 机电脑的多核处理器一般运行在 SMP 模式,实现的功能较为复杂,但对实时性的要求不高。
如下是AMP 与 SMP 运行模式的框图

ZYNQ-7000 SOC 提供了两个 Cortex-A9 处理器,这两个处理器在 AMP 的机制下,可以运行各自独立
的操作系统或者裸机应用程序。本次试验采用的是双核 AMP 的运行模式,两个 CPU 分别运行不同的裸机应用程序。
ZYNQ 中的 OCM 包括 256KB 的 RAM 和 128KB 的 ROM(BootRom),其中可以被两个 CPU 进行改
写的,就是 256KB 的 RAM。OCM 分为 4 个 64KB RAM,存储空间较小,而外置的 DDR3 存储器一般存储空间较大。当两个 CPU 需要进行大量数据交互的时候,可以使用 DDR3 存储器作为共享内存;而当交互的数据较少时,既可以使用 OCM 作为共享内存,也可以使用 DDR3 存储器作为共享内存。值得一提的是,当交互的数据量较少时,OCM 作为共享内存有着独特的优势,与 DDR 内存相比,OCM 提供了非常高的性能和来自两个处理器的低延迟访问。
本次试验的两个 CPU 都会使用到串口,并且 CPU0 会对 OCM 进行写操作,CPU1 对 OCM 进行读操作,这就要求共享外设和内存不能同时访问,以免产生冲突。我们可以利用软件产生中断(SGI)的方式来规避冲突。在初始状态下,CPU0 先使用串口,在接收到用户数据后,将数据写入 OCM 中,并产生中断来触发CPU1 中断,此时 CPU0 不在访问串口和 OCM;CPU1 触发中断后,开始访问串口和 OCM,先从 OCM 中读出数据,通过串口来输出信息,并产生中断触发 CPU0 中断,之后不再访问串口和 OCM;CPU0 接收到CPU1 的中断后,此时可以重新通过串口来接收用户输入的数据,从而避免了共享外设和内存的同时访问。
二、硬件设计

三、软件设计
在SDK中新建两个APP ,一个是a9_cpu0_app,另一个是a9_cpu1_app,配置CPU的时候一个选cpu0,另一个选cpu1

两个工程都用hello world模板

其中,a9_cpu0_app里面的代码如下
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xscugic.h"
#include "sleep.h"
#define sev() __asm__("sev")
#define CPU1STARTADR 0xfffffff0 //存放 CPU1 应用起始地址的地址
#define CPU1STARTMEM 0x10000000 //CPU1 应用起始地址
//启动 CPU1,用于固化程序
void StartCpu1(void)
{
Xil_Out32(CPU1STARTADR,CPU1STARTMEM); //向 CPU1STARTADR(0Xffffffff0)地址写入 CPU1 的访问内存基地址
dmb(); //等待内存写入完成(同步)
sev(); //通过"SEV"指令唤醒 CPU1 并跳转至相应的程序
}
int main()
{
init_platform();
Xil_SetTlbAttributes(0xffff0000,0x14de2); //禁用 0xfffffff0 的 Cache 属性
print("Hello World cpu0\n\r");
print("try to start cpu1...\r\n");
StartCpu1();
while(1)
{
print("Hello World cpu0\r\n");
sleep(1);
}
cleanup_platform();
return 0;
}
里面包含了启动CPU1的函数,实际我们启动DEBUG的时候这个函数不会发生作用,需要程序固化到FLASH之后,启动才会生效。
a9_cpu1_app里面的代码如下
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "sleep.h"
int main()
{
init_platform();
sleep(1);
print("Hello World cpu1\n\r");
while(1)
{
print("Hello World cpu1\r\n");
sleep(1);
}
cleanup_platform();
return 0;
}
Xil_SetTlbAttributes(0xffff0000,0x14de2);是禁用 0xfffffff0 的 Cache 属性
接下来设置cpu0的APP地址设置,起始地址和大小

设置cpu1的APP地址设置,起始地址和大小

注意上图中第一行左侧为基地址,右侧为存储空间大小,以字节为单位,存储空间大小设置
为 0x00100000(十进制 1048576),即 1MByte(1048576/1024/1024)。修改完成后,按下“Ctrl”+“S”保存。另外上图中的 ps7_ram_0 和 ps_ram_1 为 OCM 共享内存的基地址和存储空间大小。注意,两个CPU的基地址要设置不同
DEBUG配置如下


接下来验证
选择A9 #0处理器,点击运行,串口打印CPU0

在选择A9 #1,CPU1也运行起来了,二者交互打印信息

四、固化到FLASH
我们SDK新建一个FSBL启动工程

FSBL工程新建完成之后,等待SDK自动编译结束
结束之后打包BIN文件,固化到FLASH


然后我们烧写到FLASH,根据板子的背面设置成QSPI模式

注意,烧写之前要先断开开发板的SDK连接

烧写结束,板子重启,打开串口助手,可以看到两个处理器愉快的运行起来了
