SoC 第三讲 AMP架构双核应用程序开发和软中断处理(一)
本节主要涉及到 裸机 使用两个ARM CPU 处理器(AMP架构) 来跑不同的应用程序,即使用共享内存时进行交互,并将其写进SD卡。本节涉及到CPU核之间的中断,关于PS-SL之间的中断的例程可以参考下一讲。
一、AMP 非对称处理器架构
从软件的角度来看,多核处理器的运行模式有三种:
AMP(非对称多进程):多个核心相对独立的运行不同的任务,每个核心可能运行不同的操作系统或裸机程序,但是有一个主要核心,用来控制整个系统以及其它从核心 。即 zynq 使CPU0跑一个程序,CPU1 跑一个程序。然后通过一定方式进行数据交互。 具体可以通过中断、共享内存的方式可以交互
SMP(对称多进程):一个操作系统同等的管理各个内核,例如PC机 。
BMP(受约束多进程):与SMP类似,但开发者可以指定将某个任务仅在某个指定内核上执行
默认情况下,ZYNQ仅运行一个CPU,这里主要研究AMP模式下,两个CPU同时运行。
二、搭建 BD 工程(Vivado)
- 创建BD,添加ZYNQ IP核,并进入PS的配置界面 (需要强调一点的是根据板卡进行配置)
- 在PS-PL中取消时钟复位接口FCLK_RESTFCLK_REST 和 M AXI GP0 (默认会有,这次用不到)
- MIO configuration 中bank1 为1.8 V。开启UART1(接口 48,49)和SD卡。
- 在clock configuration和DDR configuration中,关闭PL时钟模块FCLK,并设置DDR型号(根据自己的板卡)
- Run Block Automation原理图如下
- 生成顶层文件Creat HDL Wrapper
- 综合、编译、生成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启动,正常情况下将正常工作
注:若是没出现正常的现象,则可能是:
- SD卡的bank电压及引脚速度选择错误
- CPU0和CPU1的DDR地址冲突
- 未在FSBL的main.c中加入StartCpu1函数
五、总结一下:
本节主要利用 ZYNQ ARM 双核CPU来实现核间的软中断
在SDK中注意的地方有:
- 在分别建立CPU0、1的应用程序时,需要明确设置程序运行CPU的位置。
- 注意DDR空间的分配,两个CPU地址分配不能重叠
- 注意双核AMP编译的标志需要设置。
- 在今后的开发应用中可能遇到数组声明空间会比较大的问题,导致堆栈不够用,在CPU执行数组的时候会出现系统崩溃,解决的办法:
也是在lscript.ld文件中设置,将堆栈的空间设置大一点会避免这一问题。