[X]关闭
0

S02-CH31 基于AMP实现裸机双核运行

摘要: 软件版本:VIVADO2017.4操作系统:WIN10 64bit硬件平台:适用米联客 ZYNQ系列开发板米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!31.1概述 ZYNQ中存在两个独立的ARM核,在很多应用场景中 ...

软件版本:VIVADO2017.4

操作系统:WIN10 64bit

硬件平台:适用米联客 ZYNQ系列开发板

米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!

31.1概述

      ZYNQ中存在两个独立的ARM核,在很多应用场景中往往只需使用其中的1个核心即可。然而,对于复杂的设计,例如多任务,并行控制、处理等,单个核心将难以胜任。因此,为了尽可能发挥ZYNQ中双ARM核的优势和性能,进行双核应用的开发显得尤为重要。同时,也进一步为Xilinx下一代MPSOC多核异构处理器的使用打下基础。

     在ZYNQ中实现双ARM核AMP应用可以参考Xilinx官方的XAPP1078和XAPP1079。在SDK中也有用于双核应用开发的openamp库可以使用。

本例程未使用openamp库,通过自行设计的代码实现了一个简单的双核应用,旨在说明:

  1. 双核通过软件中断进行核间通信的原理及方法。
  2. 双核通过共享内存进行数据交互的基本原理和设计方法。
  3. 双核协同工作的基本模式。
  4. 双核BOOT的方法。

31.2基本原理

      本例程在ARM核的CORE0建立一个TCP Server,CORE0通过TCP Server从外部TCP Client接收数据。当CORE0接收到1个TCP包后,将数据复制到DDR3中的缓存区域内,然后将数据信息(长度、首地址)放入CORE0和CORE1在DDR3的共享内存中。

      接着,CORE0通过触发软件中断通知CORE1数据信息已存入共享内存中,CORE1接到中断后从共享内存读出数据信息,并将对应长度的数据复制到缓存区中,然后通过UART将数据输出。当CORE1通过串口完成数据输出后,同样通过触发软件中断通知CORE0数据发送已完成,CORE0接到中断后通过串口打印完成信息,然后开始接收下一个TCP包,重复上述过程。

       最后,通过FSBL实现双核的QSPI BOOT。

31.2.1软件中断

      在UG585的Interrupts 部分7.2.1章节可以找到关于软件中断(SGI)的说明。简而言之,软件中断就是CPU自己产生的中断,可用于触发自身和其他CPU。ZYNQ中共有16个软件中断可以使用,对应的中断号为0~15,如下图所示。通过软件中断可以实现CPU之间的相互通信。本例程使用了编号1和2的软件中断。

31.2.2共享内存通信

      所谓共享内存就是,CORE0和CORE1在DDR3内存中约定一块地址及长度已知的内存区域。然后,两者之间便可通过这片区域进行数据的传递。

     两个核心各自拥有独立的L1 DCache,并且共享同一个L2 DCache,在ZYNQ中存在一个Snoop Control Unit (SCU)用于维护CORE0和CORE1的L1 DCache与L2 DCache之间的一致性,无需用户干预。因此,虽然CORE0和CORE1的共享内存区域位于DDR中,两者之间的数据传递并不需要考虑DCache一致性的维护。但是,为了更好阐明多级存储器结构的特性以及DCache一致性维护的问题,本例程在两核间通过DDR3共享内存进行数据交互时加入了DCache一致性操作,最终达到的效果与不使用DCache一致性操作时相同。

      DCache一致性维护的原理为:在多级存储器结构中,CPU通过1级或多级Cache与DDR产生连接,CPU本身不直接访问DDR,而是通过Cache访问DDR。Cache中始终会暂存一小部分(通常是KB~几MB量级)CPU最近访问的DDR某些地址区域中的数据。因此,在应用程序中对DDR进行读或写操作,实际上都是CPU对Cache进行读或写操作。当DDR中某个地址范围内的数据突然被除CPU以外的Master(如DMA)改变时,若此时Cache中保存了这些区域的数据,且这些数据在Cache中状态为有效时,当CPU需要再次读取DDR这片区域的数据时,就不会让Cache去读取DDR中此区域内最新的数据来更新Cache,再从Cache里读取最新的数据,而是直接从Cache中读取原来的旧数据,显然这不是我们所期望的结果。这时,便引入了所谓的Cache一致性问题。

      ZYNQ中存在ICache和DCache,ICache用于缓存可执行程序,DCache用于缓存数据。一般情况下,用于保存可执行程序的DDR地址范围不会被除CPU以外的对象访问。因此,一般不存在ICache的一致性问题。而DCache在很多应用中却经常会被除CPU以外的对象访问,所以存在一致性问题。

      ZYNQ中维护DCache一致性的方法为:写入方将数据写入DDR对应地址区域后,需将残留在DCache中相应地址范围内的数据全部刷入DDR3中。读取方在从DDR相应地址读取数据之前,需将DCache中DDR相应地址范围内的数据全部设置为invalid,然后CPU会再次通过DCache从DDR3中读取该地址范围内最新的数据。

31.2.3双核BOOT

      裸机双核BOOT的方法参考自Xilinx的XAPP1079。首先,通过FSBL实现CORE0的BOOT。当CORE0启动进入main函数后,配合FSBL再实现CORE1的启动。

      具体原理参考XAPP1079,此处不作赘述。

31.3驱动程序

      CORE0工程的驱动程序文件位于c_driver文件夹中的core0文件夹中,CORE1工程的驱动程序文件位于c_driver文件夹中的core1文件夹中。需要说明的是,core0和core1的工程在DDR所占用的地址区域进行如下划分:

  • CORE0: 0x00100000~0x01FFFFFF,见下图core0的lscript.ld文件设置。

  • CORE1: 0x02000000~0x02FFFFFF,见下图core1的lscript.ld文件设置。

 设计双核应用,两个工程的内存分配是一个关键的前提,程序所占用的DDR空间不能发生重合,应完全分隔开。

31.3.1 CORE0工程

31.3.1.1 main函数

main函数的完成的功能如下:

  • 配置Timer及其中断
  • 初始化中断控制器
  • 初始化软件中断
  • 初始化LWIP协议栈
  • 建立TCP Server,启动Timer
  • 配合FSBL启动CORE1
  • 持续从LWIP协议栈接收数据,若接收到CORE1触发的软件中断,则作出响应。

31.3.1.2建立TCP Server

     基于LWIP库在ARM中建立一个TCP Server,IP地址为192.168.1.10,端口号为5010。

  • lwip库设置

     见“基于TCP的QSPI Flash bin文件网络烧写”例程。

  • 程序解析

见“基于TCP的QSPI Flash bin文件网络烧写”例程。

31.3.1.3 初始化软件中断

通过software_intr.c中的Init_Software_Intr()函数初始化软件中断,对于CORE0,该函数完成如下功能。

  • 初始化CORE1到CORE0的软件中断,中断号为2,设置中断优先级和触发方式。
  • 绑定该中断的中断服务函数为Cpu0_Intr_Hanedler。
  • 将该中断映射至CORE0,并使能。

31.3.1.4 启动CORE1

通过main.c中的Start_cpu1()函数配合FSBL完成CORE1的启动。Start_cpu1()原理如下:

  • 将FSBL工程位于OCM中的.stack区域和vector table区域的cache禁用。
  • 将CORE1工程代码的位于DDR中的起始地址0x02000000(见core1的lscript.ld)写入FSBL的vector table中定义的cpu1_catch地址内。CORE1工程代码的起始地址由如下宏所定义。若core1的lscript.ld中代码位于DDR的起始地址发生更改,则该宏定义也必须进行相应更改。

            #define APP_CPU1_ADDR 0x02000000

  • 复位CORE1及其时钟
  • 使能CORE1及其时钟

31.3.1.5 数据写入共享内存

     CORE0通过TCP协议接收外部TCP Client发送的数据包,并将数据信息写入CORE0和CORE1共享内存中。共享内存首地址定义为:

     #define SHARED_BASE_ADDR0x08000000

     为共享内存区域在shared_mem.h中定义结构体shared region,其中包含了两核间所需交互的数据长度及数据指针。

typedefstruct

{

u32data_length;

u8* dataload;

}shared_region;

      CORE0通过shared_mem.c中的put_data_to_region()函数将所需传递的数据长度及指针存入shared region结构体中。在put_data_to_region函数中调用Xil_DCacheFlushRange函数将DCache中该数据指针所指向内存区域的数据刷入DDR中,进行DCache一致性维护。

      CORE0将此结构体放置于共享内存首地址SHARED_BASE_ADDR,CORE1便可以从该地址读取CORE0所需传递的数据信息,从而进一步获取数据。

31.3.1.6 触发软件中断

      CORE0通过调用shared_mem.c中的Gen_Software_Intr函数触发CORE0到CORE1的软件中断,中断号为1,中断目标CPU设置为CORE1。

31.3.1.7响应软件中断

      当CORE1向CORE0触发中断号为2的软件中断时,CORE0调用Cpu0_Intr_Hanedler函数响应此中断。然后,CORE0通过串口打印相应信息。

31.3.2 CORE1工程

31.3.2.1 main函数

main函数的完成的功能如下:

  • 配置Timer及其中断
  • 初始化中断控制器
  • 初始化软件中断
  • 等待CORE0触发的软件中断,将CORE0通过共享内存传递的数据由串口输出
  • 串口数据输出完成后触发CORE1到CORE0的软件中断

31.3.2.2初始化软件中断

     通过software_intr.c中的Init_Software_Intr()函数初始化软件中断,对于CORE1,该函数完成如下功能。

  • 初始化CORE0到CORE1的软件中断,中断号为1,设置中断优先级和触发方式。
  • 绑定该中断的中断服务函数为Cp1_Intr_Hanedler。
  • 将该中断映射至CORE1,并使能。

31.3.2.3响应软件中断

      当CORE0向CORE1触发中断号为1的软件中断时,CORE1调用Cpu1_Intr_Hanedler函数响应此中断。然后,CORE1开始从共享内存中读取CORE0所传递的数据。

31.3.2.4共享内存数据读出

      CORE1从共享内存区域SHARED_BASE_ADD地址中获取CORE0传递的数据信息,通过shared_mem.c中的get_data_from_region()函数将CORE0传递的数据长度及指针读出,并复制到本地缓存中。

      在get_data_from_region函数中,在将CORE0传递的数据复制到本地缓存区域之前,调用Xil_DCacheInvalidateRange函数将DCache中该数据指针所指向内存区域的数据设置为invalid,进行DCache一致性维护。

31.3.2.5触发软件中断

      当CORE1将从共享内存中读取的数据通过串口输出完毕后,CORE1通过调用shared_mem.c中的Gen_Software_Intr函数触发CORE1到CORE0的软件中断,中断号为2,中断目标CPU设置为CORE0。以此通知CORE0,CORE1串口输出数据完成。

31.4工程创建及设置关键步骤

创建工程,搭建硬件系统。

Step1:创建硬件工程,添加ZYNQ IP,并配置DDR型号、时钟、串口、网口。

     

Step2:如下对ZYNQ IP进行修改

最后生成的ZYNQ IP

Step3:综合编译,生成bit文件。导出,加载SDK。

SDK中创建工程。

Step4:创建CORE0工程。新建工程tcp_lwip_core0,并修改BPS包,修改见“基于TCP的QSPI Flash bin文件网络烧写”例程。

Step5:创建CORE1工程。新建工程uart_core1,如下所示进行修改。

  • CORE1工程创建时Processor要选择ps_cortexa7_1,不要选成ps_cortexa7_0,如下图所示。

  • 设置CORE1工程的编译选项

      在CORE1工程的bsp中要增加编译选项“-DUSE_AMP=1”,如下图所示。该编译选项将影响到CORE1工程代码里中断控制器SCUGIC的初始化函数以及Cache操作函数的编译,若不增加该选项,可能会出现CORE0和CORE1中断异常和Cache一致性维护异常。

Step6:对CORE0 和CORE1的 lscript.ld文件重新设置,参考5.3。

  • CORE0: 0x00100000~0x01FFFFFF,见下图core0的lscript.ld文件设置。

  • CORE1: 0x02000000~0x02FFFFFF,见下图core1的lscript.ld文件设置。

31.5工程调试关键步骤

Step1 :system debugger里同时添加core0和core1工程文件,如下图所示。

Step2 : Debug时先让CORE0运行。

Step3:连接串口,连接网口。

      在SDK中下载程序至ZYNQ中。打开网络调试助手,选择TCP Client方式,输入ARM中定义的TCP Server的IP地址和端口号,然后点击连接按键,建立TCP连接。

此时串口输出信息。

Step4 :再让CORE1运行。

此时串口输出信息。

在网络调试助手中输入任意文字发送,如图所示。

此时串口输出信息。

继续通过网络调试助手发送信息,串口输出如下图所示。

31.6生成BOOT.bin及双核BOOT验证

31.6.1 生成BOOT.bin

创建FSBL文件。

对main.c文件进行修改,在main.c中增加以下代码

#define sev() __asm__("sev")

#define CPU1STARTADR 0xFFFFFFF0

#define CPU1STARTMEM 0x02000000

void StartCpu1(void)

{

    #if 1

    fsbl_printf(DEBUG_GENERAL,"FSBL: Write the address of the application for CPU 1 to 0xFFFFFFF0\n\r");

    Xil_Out32(CPU1STARTADR, CPU1STARTMEM);

    dmb(); //waits until write has finished

    fsbl_printf(DEBUG_GENERAL,"FSBL: Execute the SEV instruction to cause CPU 1 to wake up and jump to the application\n\r");

    sev();

    #endif

}

int main(void)

{

u32 BootModeRegister = 0;

u32 HandoffAddress = 0;

u32 Status = XST_SUCCESS;

找到Load boot image的位置,把CPU1的启动函数,在此调用:

/*

     * Load boot image

     */

    HandoffAddress = LoadBootImage();


    fsbl_printf(DEBUG_INFO,"Handoff Address: 0x%08lx\r\n",HandoffAddress);

    StartCpu1();   /*add starting cpu1*/

创建UBOOT.BIN

31.6.2 双核BOOT验证

      将BOOT.bin文件烧入QSPI flash中,重启开发板。SDK串口终端输出信息,如下图所示。当提示“core1:application start”,代表CORE0和CORE1都已成功启动。


        使用网络调试助手进行TCP连接并发送数据,串口输出如下图所示。此时,验证了双核BOOT后,CORE0和CORE1均运行正常。



路过

雷人

握手

鲜花

鸡蛋

最新评论

本文作者
2019-9-6 19:40
  • 7
    粉丝
  • 3450
    阅读
  • 0
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜