简介
多核处理器从多核的结构上是否一致,分为两种基本架构:同构多核架构和异构多核架构。同构多核处
理器是指系统中的处理器在结构上是相同的;而异构处理器是指系统中的处理器在结构上是不同的,这些处理器可以是通用处理器,也可以是解决某些特定应用的专用硬核。同构多核架构相比于异构多核架构,在硬件和软件设计上较为简单,通用性较高。但在某些特定应用场合下,如异构多核架构专用的硬件加速硬核,异构多核架构的性能会更高。
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 的运行模式基本没有开销问题,在运行裸机应用程序时,甚至完全没有开销,比较适合实时性
要求较高的应用,但需要精心定制的软件设计来实现处理器资源共享和处理器间通信。如电力控制保护设备通常需要与人机接口实现复杂的通信和高实时性的计算能力,一般采用 AMP 运行模式,一个处理器运行Linux 操作系统,另一个处理器运行裸机应用程序,从而兼顾了电力系统控制设备需要的复杂功能和实时性。AMP 和 SMP 运行模式的框图如图 所示。
AMP 运行模式给开发者提供了一个与传统单核 CPU 系统相类似的运行环境,使得开发者已有的经验
和知识可以继续加以利用;同时,也为程序的移植提供了很大的便利性。本次试验采用的是双核 AMP 的运行模式,两个 CPU 分别运行不同的裸机应用程序。ZYNQ SOC 提供的两个 Cortex-A9 处理器,都具有各自私有的资源,同时也有一些共享的资源。私有资源有 L1 指令缓存、L1 数据缓存、私有定时器等。共享的资源有 L2 Cache、DDR 存储器、外设和 OCM(On Chip Memory)等。在 AMP 运行模式下,这两个处理器彼此隔离、分别运行。但在访问共享资源或者外设时,要注意避免冲突,否则程序在运行时可能会出现问题。我们可以通过类似互斥的方式来实现裸机情况下简单的双核使用,在 ZYNQ 系统中,可以利用软件产生中断(SGI,Software Generated Interrupts)来避免访问的冲突。ZYNQ 系统中的每个 CPU 都能用 SGI 来中断自己、另一个 CPU 或同时中断两个 CPU。向软件产生的中断寄存器(ICDSGIR)写入中断编号并指向目标 CPU,就产生了一个 SGI。每个 CPU 各自有一组 SGI 寄存器,可以产生 16 个软件产生中断中的一个或多个。图 列出了 SGI 的中断 ID 号,范围是 0~15。
SGI 可以触发 CPU 产生中断,但是如果想要传递数据的话,需要利用共享的内存实现数据的交互。共
享内存指 CPU0 和 CPU1 在内存中约定一块地址及长度已知的内存区域,两个 CPU 通过读写共享内存中的数据来实现互相通信,而利用 CPU 产生的软件中断可以避免对共享内存的同时访问。
ZYNQ 中的 OCM 共有 256KB,分为 4 个 64KB,而外置的 DDR3 存储器一般存储空间较大。当两个
CPU 需要进行大量数据交互的时候,可以使用 DDR3 存储器作为共享内存;而当交互的数据较少时,既可以使用 OCM 作为共享内存,也可以使用 DDR3 存储器作为共享内存。值得一提的是,当交互的数据量较少时,OCM 作为共享内存有着独特的优势,与 DDR 内存相比,OCM 提供了非常高的性能和来自两个处理器的低延迟访问。需要注意的是,无论是 OCM 还是 DDR3 存储器作为共享内存,在访问之前,需要禁止存储空间的 Cache(缓存)功能,这样 CPU 能够及时读到该地址内存中变化的数据,以避免两个 CPU 访问共享内存的一致性问题。通过 Xil_SetTlbAttributes(INTPTR Addr, u32 attrib)函数禁用 Cache 属性,第一个参数为共享内存的基地址,第二个参数为设置内存的参数,包括是否禁用 Cache 等。Xilinx 官方提供了两个双核 AMP 的应用文档,“XAPP1078” 是基于两个 Linux 操作系统的双核 AMP应用文档,“XAPP1079”是基于两个裸机应用程序的双核 AMP 应用文档,感兴趣的朋友可以参考下。
BD设计
软件设计
CPU0的代码
#include "xparameters.h"
#include "xscugic.h"
#include "xil_printf.h"
#include "xil_exception.h"
#include "xil_mmu.h"
#include "stdio.h"
//宏定义
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //中断ID
#define SHARE_BASE 0xffff0000 //共享OCM首地址
#define CPU1_START_ADDR 0xffffff00 //CPU1开始地址
#define CPU1_START_MEM 0x10000000 //CPU1程序开始地址
u16 SoftIntrIdToCpu0 = 0 ;
u16 SoftIntrIdToCpu1 = 1 ;
#define CPU1_ID 2 //CPU1 ID,0bxxxxxx1x指向CPU1
//"SEV"指令唤醒CPU1并跳转至相应的程序
#define sev() __asm__("sev")
//函数声明
void start_cpu1();
void cpu0IntrInit(XScuGic *intc_ptr);
void IntrHandler(void *CallbackRef);
//全局变量
XScuGic Intc; //中断控制器驱动
int rec_freq_flag = 0; //接收到来自cpu1中断的标志
int freq; //频率调节
//CPU0 main函数
int main()
{
//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(SHARE_BASE,0x14de2); //禁用Cache缓存
//启动CPU1
start_cpu1();
//CPU0中断初始化
cpu0IntrInit(&Intc);
while(1){
if(rec_freq_flag == 0){
xil_printf("This is CPU0,Please input the numbers 1~3 to change "
"breath led frequency\r\n");
scanf("%d",&freq);
if(freq >= 1 && freq <=3){
xil_printf("You input number is %d\r\n",freq);
//将参数写入共享的内存
Xil_Out8(SHARE_BASE,freq);
//给CPU1发送软件中断
XScuGic_SoftwareIntr(&Intc,SoftIntrIdToCpu1,CPU1_ID);
rec_freq_flag = 1;
}
else{
xil_printf("Error,The number range is 1~3,please input another number\r\n");
xil_printf("\r\n");
}
}
}
return 0 ;
}
//启动CPU1
void start_cpu1()
{
//向 CPU1_START_ADDR(0Xffffffff0)地址写入 CPU1 的访问内存基地址
Xil_Out32(CPU1_START_ADDR, CPU1_START_MEM);
dmb();
sev(); //唤醒CPU1并跳转至相应的程序
}
//CPU0中断初始化
void cpu0IntrInit(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_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_ptr);
Xil_ExceptionEnable();
//关联中断源及中断处理函数
XScuGic_Connect(intc_ptr, SoftIntrIdToCpu0,
(Xil_ExceptionHandler)IntrHandler, (void *)intc_ptr);
XScuGic_Enable(intc_ptr, SoftIntrIdToCpu0); //CPU0软件中断
}
//中断服务函数
void IntrHandler(void *CallbackRef)
{
xil_printf("This is CPU0,Receive software interrupt from CPU1\r\n");
xil_printf("\r\n");
rec_freq_flag = 0;
}
CPU1代码的部分
#include "xparameters.h"
#include "xscugic.h"
#include "xil_printf.h"
#include "xil_exception.h"
#include "xil_mmu.h"
#include "stdio.h"
#include "AXI_PWM.h"
//宏定义
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //中断ID
#define SHARE_BASE 0xffff0000 //共享OCM首地址
u16 SoftIntrIdToCpu0 = 0 ;
u16 SoftIntrIdToCpu1 = 1 ;
#define CPU0_ID 1 //CPU1 ID,0bxxxxxxx1指向CPU0
#define PWM_IP_BASEADDR XPAR_AXI_PWM_0_S00_AXI_BASEADDR //PWM IP基地址
#define PWM_IP_REG0 AXI_PWM_S00_AXI_SLV_REG0_OFFSET //PWM IP寄存器地址0
#define PWM_IP_REG1 AXI_PWM_S00_AXI_SLV_REG1_OFFSET //PWM IP寄存器地址1
//函数声明
void cpu1IntrInit(XScuGic *intc_ptr);
void IntrHandler(void *CallbackRef);
//全局变量
XScuGic Intc; //中断控制器驱动
int intr_flag = 0; //软件中断的标志
int freq; //频率设置
//CPU1 main函数
int main()
{
int freq_step = 0;
//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(SHARE_BASE,0x14de2); //禁用Cache缓存
//CPU1中断初始化
cpu1IntrInit(&Intc);
while(1){
if(intr_flag){
freq = Xil_In8(SHARE_BASE); //从共享内存中读出数据
xil_printf("CUP1 Received data is %d\r\n",freq) ;
switch(freq){
case 1 : freq_step = 50;break;
case 2 : freq_step = 200;break;
case 3 : freq_step = 400;break;
default : freq_step = 50;break;
}
//设置呼吸灯频率
AXI_PWM_mWriteReg(PWM_IP_BASEADDR,PWM_IP_REG0,(0x00000000|freq_step));
//给CPU0发送软件中断
XScuGic_SoftwareIntr(&Intc,SoftIntrIdToCpu0,CPU0_ID);
intr_flag = 0;
}
}
return 0 ;
}
//CPU1中断初始化
void cpu1IntrInit(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_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_ptr);
Xil_ExceptionEnable();
//关联中断源及中断处理函数
XScuGic_Connect(intc_ptr, SoftIntrIdToCpu1,
(Xil_ExceptionHandler)IntrHandler, (void *)intc_ptr);
XScuGic_Enable(intc_ptr, SoftIntrIdToCpu1); //使能CPU1软件中断
}
//中断服务函数
void IntrHandler(void *CallbackRef)
{
xil_printf("This is CUP1,Soft Interrupt from CPU0\r\n") ;
intr_flag = 1;
}