一、简介
多核处理器从多核的结构上是否一致,分为两种基本架构:同构多核架构和异构多核架构。同构多核处理器是指系统中的处理器在结构上是相同的;而异构处理器是指系统中的处理器在结构上是不同的,这些处理器可以是通用处理器,也可以是解决某些特定应用的专用硬核。同构多核架构相比于异构多核架构,在硬件和软件设计上较为简单,通用性较高。但在某些特定应用场合下,如异构多核架构专用的硬件加速硬核,异构多核架构的性能会更高。
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 运行模式的框图
![](https://i-blog.csdnimg.cn/blog_migrate/9f0e3b6d72255bc8388e05f37d385cf0.png)
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 的中断后,此时可以重新通过串口来接收用户输入的数据,从而避免了共享外设和内存的同时访问。
二、硬件设计
![](https://i-blog.csdnimg.cn/blog_migrate/7236d2d28ce229c9cee420aa3a230e98.png)
三、软件设计
在SDK中新建两个APP ,一个是a9_cpu0_app,另一个是a9_cpu1_app,配置CPU的时候一个选cpu0,另一个选cpu1
![](https://i-blog.csdnimg.cn/blog_migrate/aafe74cca9a2f6b57375ba884a615b9a.png)
两个工程都用hello world模板
![](https://i-blog.csdnimg.cn/blog_migrate/b291c69c6db1a3d6bab59fea744694b9.png)
其中,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地址设置,起始地址和大小
![](https://i-blog.csdnimg.cn/blog_migrate/279e721141a9b7f121156a9861153a63.png)
设置cpu1的APP地址设置,起始地址和大小
![](https://i-blog.csdnimg.cn/blog_migrate/388640bafef64361f056dd5f925b56bf.png)
注意上图中第一行左侧为基地址,右侧为存储空间大小,以字节为单位,存储空间大小设置
为 0x00100000(十进制 1048576),即 1MByte(1048576/1024/1024)。修改完成后,按下“Ctrl”+“S”保存。另外上图中的 ps7_ram_0 和 ps_ram_1 为 OCM 共享内存的基地址和存储空间大小。注意,两个CPU的基地址要设置不同
DEBUG配置如下
![](https://i-blog.csdnimg.cn/blog_migrate/ffda4754eb37c5eeaa79e497978db697.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e9aea0804fd8eb57b170637ce571a57a.png)
接下来验证
选择A9 #0处理器,点击运行,串口打印CPU0
![](https://i-blog.csdnimg.cn/blog_migrate/371f6cc61c5a468e60d6aa24f758fc88.png)
在选择A9 #1,CPU1也运行起来了,二者交互打印信息
![](https://i-blog.csdnimg.cn/blog_migrate/bf5104c4cf1be1570c50260e694719f3.png)
四、固化到FLASH
我们SDK新建一个FSBL启动工程
![](https://i-blog.csdnimg.cn/blog_migrate/b0ce4ef98898d5f49b093fc647cee515.png)
FSBL工程新建完成之后,等待SDK自动编译结束
结束之后打包BIN文件,固化到FLASH
![](https://i-blog.csdnimg.cn/blog_migrate/1022f723a2f7b948525648bc86528e06.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c8653ff6cca72a89317cf906bb47ffe7.png)
然后我们烧写到FLASH,根据板子的背面设置成QSPI模式
![](https://i-blog.csdnimg.cn/blog_migrate/6d45f8c51117381b84f3619cf2a8b2f5.png)
注意,烧写之前要先断开开发板的SDK连接
![](https://i-blog.csdnimg.cn/blog_migrate/44551930611362ed580311cc51fb0b6a.png)
烧写结束,板子重启,打开串口助手,可以看到两个处理器愉快的运行起来了
![](https://i-blog.csdnimg.cn/blog_migrate/fb1c529cad546a66d7a82da8caf5f7a9.png)