SoC 第三讲 AMP架构双核应用程序开发和软中断处理(一)——双核应用程序

本文详细介绍了如何在ZYNQ平台的AMP架构下,利用双核ARM处理器进行应用程序开发,包括共享内存交互、软中断处理、DDR内存分配、SD卡固化等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SoC 第三讲 AMP架构双核应用程序开发和软中断处理(一)
本节主要涉及到 裸机 使用两个ARM CPU 处理器(AMP架构) 来跑不同的应用程序,即使用共享内存时进行交互,并将其写进SD卡。本节涉及到CPU核之间的中断,关于PS-SL之间的中断的例程可以参考下一讲。

一、AMP 非对称处理器架构

从软件的角度来看,多核处理器的运行模式有三种:

AMP(非对称多进程):多个核心相对独立的运行不同的任务,每个核心可能运行不同的操作系统或裸机程序,但是有一个主要核心,用来控制整个系统以及其它从核心 。即 zynq 使CPU0跑一个程序,CPU1 跑一个程序。然后通过一定方式进行数据交互。 具体可以通过中断、共享内存的方式可以交互

SMP(对称多进程):一个操作系统同等的管理各个内核,例如PC机 。

BMP(受约束多进程):与SMP类似,但开发者可以指定将某个任务仅在某个指定内核上执行

默认情况下,ZYNQ仅运行一个CPU,这里主要研究AMP模式下,两个CPU同时运行。

二、搭建 BD 工程(Vivado)
  1. 创建BD,添加ZYNQ IP核,并进入PS的配置界面 (需要强调一点的是根据板卡进行配置)
  2. 在PS-PL中取消时钟复位接口FCLK_RESTFCLK_RESTM AXI GP0 (默认会有,这次用不到)
  3. MIO configuration 中bank1 为1.8 V。开启UART1(接口 48,49)和SD卡
  4. 在clock configuration和DDR configuration中,关闭PL时钟模块FCLK,并设置DDR型号(根据自己的板卡)
  5. Run Block Automation原理图如下
    在这里插入图片描述
  6. 生成顶层文件Creat HDL Wrapper
  7. 综合、编译、生成bit流,导出硬件并运行SDK
三、搭建C语言环境(SDK)
1. 创建CPU0、CPU1上应用空程序amp_cpu0、amp_cpu1。

CPU0 具体代码如下:

    #include <stdio.h>
    
    #define COMM_VAL	(*(volatile unsigned long *)(0xffff0000))
    //首先进行定义共享内存地址。因为我们需要访问其数据,因此加个*访问其寄存器中的地址 0xffff0000对应OCM3
    
    int main()
    {
    	COMM_VAL = 0;    	Xil_SetTlbAttributes(0xffff0000,0x14de2); //disable cache OCM
    	
    	while(1) {
    		print("Hello CPU0\n\r");
    		COMM_VAL = 1;
    		while(COMM_VAL == 1){
    		}    /* 循环的读取COMM_VAL的值是否改变,
    		*/
    		usleep(2000000);    		
    	}
        return 0;
    }

同样CPU1 代码:

#include <stdio.h>
#define COMM_VAL (*(volatile unsigned long *)(0xffff0000))//将地址转改为指针

int main()
{	    Xil_SetTlbAttributes(0xffff0000,0x14de2);
	    while(1){
	    	 print("Hello World CPU1 \n\r");
	    	 while(COMM_VAL == 0){
	    	 }
	    	 print("Hello World CPU1 \n\r");
	    	 usleep(100);  //使CPU1,CPU0打印不会冲突
	    	 COMM_VAL = 0;
	    }

    return 0;
}

代码分析,对于CPU0

首先定义一个在OCM3的变量COMM_VAL。

两个while循环,第二个while 循环是CPU0一直处于死循环状态,只有当外部CPU1将此值改为0 时,才会跳出此循环,然后开始循环外部的while,每隔2s打印一次。

由于OCM被CPU访问时,即CPU0向OCM中写数据时,CPU1也会向OCM中写数据。这机会涉及到 L2 cache一致性问题。即当CPU1往OCM中写数据时,CPU0并不知道OCM中数据被更新了(也即不知道CPU1向OCM中写数据),所以当CPU0在读OCM中的数据时,可能是L2 cache 中的数据。

这可能会造成cache中的值不能及时更新导致CPU中程序仍然处于死循环状态。因此需要关闭OCM的临时cache,使CPU0可以及时更新COMM_VAL值的变化。可以通过flash 刷新cache,使CPU0从OCM重新load,但会增加操作的复杂性,需要每次重新load。

所以在main主函数体内可以关闭 OCM 的cache

Xil_SetTlbAttributes (0xffff0000, 0x14de2);

2. DDR的空间分配

下面的图是ZYNQ的地址空间映射。在运行两个程序时,需要对地址进行配置
​​在这里插入图片描述

关于地址的空间分配和设置在文件 lscript.ld文件中,通过修改 lscript.ld 文件中的内容,可以改变在存储器中的执行位置,

需要注意的是 因为ELF文件是加载到DDR中执行的,所以两个DDR地址不能重合

下面是CPU0的DDR内存分配情况:起始地址为0x100000
在这里插入图片描述
Base Address 对应的ddr和ram的起始地址,Size 指相应的偏移量大小

3. 对DDR地址空间进行配置

在上图的 source 源文件中可以修改相应的配置。
CPU0 DDR内存的分配:0x100000~ 0x1F00000(base addr:0x100000 size:0x1E00000

在这里插入图片描述
同样CPU1的DDR内存可以配置为: 0x2000000~ 0x3F00000(base addr:0x2000000 size:0x1F00000
在这里插入图片描述
这样可以保证两个CPU的内存空间不相互重叠

4. 共享内存
#define COMM_VAL	(*(volatile unsigned long *)(0xffff0000))

首先进行定义共享内存地址。因为我们需要访问其数据,因此加个*访问其寄存器中的地址

0xffff0000对应OCM3

关键字 volatile 声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

5. CPU1的BSP SETTING中添加 -DUSE_AMP=1

使用双核AMP的编译标志需要打开,使程序可以在双核中跑起来:
因此编译的时候需要设置 CPU1 BSP,在extra-compel-flags 的 Value 中添加 -DUSE_AMP=1。
在这里插入图片描述

6. 双核 debug 调试

debug 调试设置:
在这里插入图片描述
设置好串口,先同时单步的调试,我们可以看到当CPU0执行完while循环时,会显示一致running,然后我们再单步调试CPU1时,当执行到COMM_VAL = 0 时,CPU0 停止 running。并且再串口工具中会出现CPU0、1打印。
在这里插入图片描述
当我们选择resume两个CPU时,会出现一致循环打印CPU0、1。
在这里插入图片描述

四、 SD卡固化

烧写BOOT.BIN到SD卡中,并从SD卡启动。同样适用于QSPI FLASH启动模式。

关于SD卡固化的程序,之前在FPGA基础入门篇(三) 程序的固化和下载也有详细的介绍。

1. 修改FSBL配置

需要创建FSBL(first boot load)文件,FSBL文件默认情况只加载CPU0中的程序。因此需要添加CPU1启动函数

首先创建一个FSBL的工程,在 FSBL 的 src 中找到 main.c 文件打开,在里面添加下面一段代码,用于启动CPU1:

具体代码:

#define sev() __asm__("sev")
#define CPU1STARTADR 0xFFFFFFF0
#define CPU1STARTMEM 0x2000000  //与设置的CPU1的地址相同
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
} //在load 镜像位置加入如图代码:StartCpu1();

修改Main函数为了启动core1,需要两个步骤,第一个把CPU1应用程序地址写入到0xfffffff0地址中,然后执行SEV指令启动加载CPU1应用程序。参考UG585的启动代码章节6.1.10。

前面添加的是CPU1的启动函数,再找到Load boot image的位置,将CPU1的启动函数,放置于此位置,改动后的代码段如下

	/*
	 * Load boot image
	 */
	HandoffAddress = LoadBootImage();
	fsbl_printf(DEBUG_INFO,"Handoff Address: 0x%08lx\r\n",HandoffAddress);	
	StartCpu1();   //此处为需要添加的CPU1启动
2. 创建BOOT.BIN

在这里插入图片描述

将上述文件合并为BIN文件,并放入SD卡中,ZYNQ启动模式要为SD启动,正常情况下将正常工作
注:若是没出现正常的现象,则可能是:

  1. SD卡的bank电压及引脚速度选择错误
  2. CPU0和CPU1的DDR地址冲突
  3. 未在FSBL的main.c中加入StartCpu1函数
五、总结一下:

本节主要利用 ZYNQ ARM 双核CPU来实现核间的软中断
在SDK中注意的地方有:

  1. 在分别建立CPU0、1的应用程序时,需要明确设置程序运行CPU的位置。
    在这里插入图片描述
  2. 注意DDR空间的分配,两个CPU地址分配不能重叠
  3. 注意双核AMP编译的标志需要设置。
  4. 在今后的开发应用中可能遇到数组声明空间会比较大的问题,导致堆栈不够用,在CPU执行数组的时候会出现系统崩溃,解决的办法:
    也是在lscript.ld文件中设置,将堆栈的空间设置大一点会避免这一问题。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值