一、简介
多核处理器从多核的结构上是否一致,分为两种基本架构:同构多核架构和异构多核架构。同构多核处理器是指系统中的处理器在结构上是相同的;而异构处理器是指系统中的处理器在结构上是不同的,这些处理器可以是通用处理器,也可以是解决某些特定应用的专用硬核。同构多核架构相比于异构多核架构,在硬件和软件设计上较为简单,通用性较高。但在某些特定应用场合下,如异构多核架构专用的硬件加速硬核,异构多核架构的性能会更高。
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连接
烧写结束,板子重启,打开串口助手,可以看到两个处理器愉快的运行起来了