Zenq系列FPGA双核ARM裸机异步程序实现

大门牙原创,欢迎随意转载,批评,引用。

一. 两个处理器之间的关系

Zenq系列的两个ARM处理器核心可以分别工作,其公用资源有cache、DDR、所有外设、OCM存储器等。
可以将其看成是两个线程,实际使用时,可以通过线程同步、互斥的方式来实现裸机情况下的简单双核调度与使用。

二. 实现方法

准备工作

由于两个核心的内存空间是公用的,硬件上不作区分,也就是说每个核心都可以访问到完整的DDR空间。
因此,需要在逻辑上将两个核心使用的内存空间区分开来。例如
CPU DDR
CPU0 0 ~ 200MB
CPU1 200M ~ 512MB

开发时,需要建立3个application工程
1. FSBL
2. CPU0_app
3. CPU1_app

BootLoader的改动

对于ARM架构而言,不区分程序空间和数据空间,因此程序加载时也应该加载到各自对应的空间中。对于BootLoader而言,需要完成4件事情。
1. 芯片自检,加载驱动
2. 载入bit stream,并通过VCAP模块写入FPGA
3. 载入CPU0的程序和CPU1的程序
4. 将CPU0 hand off 到CPU0的用户程序上

Linker Script的改动

对于CPU0和CPU1的工程而言,需要将其生成的ELF可执行文件的载入地址改为各自空间的首地址。通过修改linker script来实现。例如 

CPU0 Linker Script

/* Define Memories in the system */
MEMORY
{
   ps7_ddr_0_S_AXI_BASEADDR : ORIGIN = 0x00100000, LENGTH = 0x9500000
   ps7_ram_0_S_AXI_BASEADDR : ORIGIN = 0x00000000, LENGTH = 0x00030000
   ps7_ram_1_S_AXI_BASEADDR : ORIGIN = 0xFFFF0000, LENGTH = 0x0000FE00
}

CPU1 Linker Script

/* Define Memories in the system */
MEMORY
{
   ps7_ddr_0_S_AXI_BASEADDR : ORIGIN = 0x09600000, LENGTH = 0x03200000
   ps7_ram_0_S_AXI_BASEADDR : ORIGIN = 0x00000000, LENGTH = 0x00030000
   ps7_ram_1_S_AXI_BASEADDR : ORIGIN = 0xFFFF0000, LENGTH = 0x0000FE00
}

上面的例子中
将CPU0的内存基地址改为1MB,内存大小改为149MB,即CPU0的逻辑内存空间为1MB~150MB
将CPU1的内存基地址改为150MB,内存大小改为50MB,即CPU1的逻辑内存空间为150MB~200MB
系统实际内存空间为512MB,剩余的312MB留给FPGA使用

CPU1的启动

Zenq系列的双核ARM必须由CPU0先启动,再通过CPU0来启动CPU1。CPU0的程序需要将CPU1的程序入口地址设置好,再启动CPU1。实现代码如下

#define sev()   __asm__("sev");
#define CPU1_START_ADDR                 (0xFFFFFFF0)
#define SHARE_RAM_BASE_ADDR             (0xFFFF0000)
Xil_SetTlbAttributes(SHARE_RAM_BASE_ADDR, 0x14DE2);
Xil_Out32(CPU1_START_ADDR, 0x09600000);
dmb();
sev();

在汇编指令sev执行以后,CPU1即开始从0xFFFFFFF0存放的地址处开始执行。
需要注意的是,由于cache是共享资源,因此两块CPU不能同时使用。在代码中,只能有一个CPU代码执行

    Xil_ICacheEnable();
    Xil_DCacheEnable();

在OCM区构建双核控制块

由于OCM内存为两块CPU公用,且访问速度较快,可以将OCM内存区看作类似于IPC的“共享内存”,用于两块CPU程序之间的同步和互斥等。例如

struct DUAL_CORE_CTRL_STRU {
    /*  控制信号量       */
    u8 sig_CalcDone;  // 同步信号量
    u8 mutex_Printf;  // 互斥锁
    u8 spin_SD_Card;  // 自旋锁
}

然后在CPU0和CPU1的程序中分别应用这块内存区域

#define SHARE_RAM_BASE_ADDR             (0xFFFF0000)
struct DUAL_CORE_CTRL_STRU *stDualCtrl =
        (struct DUAL_CORE_CTRL_STRU*) SHARE_RAM_BASE_ADDR;

具体的信号量处理接口不再赘述,请自己造轮子 : )

posted @ 2017-03-04 09:58  大门牙  阅读(1227)  评论(0编辑  收藏  举报