目录
第一部分、基础知识
1、多核CPU三种主要运行模式
2、ZYNQ内部FSBL涉及到的启动过程
2.1、BootRom是啥?
2.2、FSBL是啥?
2.3、ARM与cortex的关系
2.4、本次实验的启动流程
第二部分、FSBL环境配置过程
1、vivado配置硬件
2、SDK新建FSBL配置CPU1程序的启动地址
3、CPU0的工程创建及程序编写
4、CPU1的工程创建及程序编写
5、BOOT.bin文件的生成办法
6、仿真的办法
7、核间中断的方式
第三部分、总结
完成两件事,介绍了ZYNQAMP模式的搭建方法以及生成BOOT.bin文件的办法。
默认情况下, 裸机程序 ZYNQ 仅运行一个 CPU,这里主要讲解 AMP 模式下,两个 CPU 同时运行的裸机程序开发方法。
ARM上电后执行BootROM,发现有合法镜像(BOOT.BIN)后判断是否要在FLASH中执行。若没有合法镜像则到下一个32K的偏移处寻找镜像。如果不XIP,则将FSBL载入到OCM中执行(因为此时是不能使用DDR的),之后开始执行FSBL。
关于XIP技术:利用XIP技术就不用把代码读到RAM中了,NOR FLASH支持XIP技术与Flash博客园
更多知识参考博客: ZYNQ_FSBL学习-CSDN博客 (下图来源于这篇博客)
BootRom可翻译为启动代码,zynq内部的BootROM存储有一段在CPU上电后/复位后固定执行的代码,这段代码用来配置一个ARM CPU和一些必要外设,从而能从一个启动设备中获取FSBL(first stage boot loader)并执行。
BootROM是一个ROM,不可写,PL的配置不是通过BootROM实现的。BootROM不能使用DDR和SCU,因为它们还没有初始化。
FSBL通常存储在FLASH中(U盘、SD卡、TF卡、SSD固态硬盘都是以FLASH闪存颗粒为介质的一类存储器),BootROM从选定的FLASH中拷贝FSBL到片上存储器OCM中执行。
参考文章:到底什么是Cortex、ARMv8、arm架构、ARM指令集、soc?知乎 (zhihu.com)
- ARM公司的名字,叫ARM:Advanced RISC Machines;
- ARM前身Acorn公司设计的第一款微处理器,叫ARM:Acorn RISC Machine;
- ARM处理器名字: 以前叫ARM9、ARM11, 新的命名规则改以Cortex命名,分别是Cortex-A,Cortex-R,Cortex-M; 这三个字母A、R、M
- ARM指令集,就是ARM架构,比如ARMv8,每个处理器都需要依赖一定的ARM架构来设计;
- SOC:各大厂商买来ARM的授权,得到ARM处理器的源代码,而后自己搞一些外围设备的IP(或者买或者自己设计),组成一个SOC,比如三星的Exynos 4412,华为的麒麟990。
第一步、ARM上电后执行BootROM,从SD卡发现有合法镜像(BOOT.BIN文件,需要提前拷贝到SD卡中)
1、Bin 文件是经过压缩的可执行文件,去掉ELF格式的东西。是直接的内存映像的表示。在系统没有加载操作系统的时候可以执行。
2、elf文件(executable and link format)文件里面包含了符号表,汇编等。
3、BIN文件是将elf文件中的代码段,数据段,还有一些自定义的段抽取出来做成的一个内存的镜像。
第二步、再将FSBL载入到OCM中执行,load boot image。(将FSBL载入到OCM的这个过程中没有使用cache,因为如果用到cache,CPU1不能及时读到OCM的内容,从而出现问题。缓存一致性)
第三步、FSBL在OCM中运行,根据启动模式的选择,FSBL从FLASH中复制启动镜像的其他部分。如果有bitstream,那么就加载到FPGA中。如果有elf应用程序存在,那么就加载到DDR中并将执行权限移交给它。
实现基本的CPU1和CPU0交互打印信息。
注意:FSBL工程的主要目的就用用来生成BOOT镜像文件。
不同器件配置不同,ZYNQ7020的bank0为LVCMOS3.3V和bank1为LVCOM1.8V。
需要注意:后面如果出现SD下载bin文件后,程序无法启动,很有可能是SD卡模块的工作电压不对,也就是Bank0和Bank1的电压配置错误。
创建顶层文件,再生成bit流文件
第一步、fsbl工程的处理器选择cpu0,因为AMP模式需要一个主要的核心来控制系统的运行
第二步、选择zynq FSBL工程模板
第三步、修改zynq FSBL工程模板
注意:这段代码尽量加在main函数的上方,这样调用的就不需要去声明,比较方便。
下面的代码中CPU1STARTADR表示的是cpu1的开始地址,这个是固定的,由厂家决定的。然后CPU1STARTMEM表示的是CPU1程序开始的地址(一般是用户分配给CPU1的DDR内存的基地址的),CPU1STARTMEM是可以根据ddr的地址,看情况给的,也就是由用户设置的,可以改变的。
//增加的代码
/*****************************配置cpu1的启动地址代码*******************************/
//更改CPU1应用程序的启动地址
#define sev() __asm__("sev")
#define CPU1STARTADR 0xFFFFFFF0//应用程序地址
#define CPU1STARTMEM 0x2000000
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
}
第四步、调用上面的函数,调用位置为580-600行,注释为load boot image(启动引导镜像文件)
为什么是这里?
因为只有在镜像文件加载完成后,才能让cpu0去触发cpu1,让cpu1去加载控制程序。
如果在这之前就去调用该函数,DDR里面都还没有镜像文件,没有程序,cpu1就跑不起来。
什么是镜像文件?
所谓镜像文件其实和rar ZIP压缩包类似,它将特定的一系列文件按照一定的格式制作成单一的文件。这里可以包含引导文件,系统文件等。
.bin就是一种常见的镜像文件的后缀。bin是binary的缩写,二进制文件。
第五步、ctrl + s保存,ctrl + b编译
尽量编译一次,避免后面调试的时候出现文件不全的问题。
第一步、再新建一个cpu0的工程。处理器一定要选择cpu0
第二步、为了方便,选择hellow world模板。
第三步、修改.c文件的名称为main.c
第四步、修改main.c文件如下
#include
#include "platform.h"
#include "xil_printf.h"
//将内存中指向0xFFFF0000地址的指针进行解引用,volatile关键词是防止这个地址的变量被程序优化掉,
//使这个地址内的存的变量更加稳定,地址必须在共享内存范围
#define COMM_VAL (*(volatile unsigned long *)(0xFFFF0000))
int main()
{
COMM_VAL=0;
//Disable cache on OCM
Xil_SetTlbAttributes(0xFFFF0000,0x14de2);//关闭cache
while(1)
{
print("Hello World cpu0 \n\r");
COMM_VAL = 1;
while(COMM_VAL == 1)
{
}
}
return 0;
}
第五步、修改cpu0的代码空间,
注意:这里应该是私有ram存储cpu0运行的程序及产生的一些实时文件,只属于cpu0。因此这里的地址范围不可以和cpu1重复。
MEMORY
{
ps7_ddr_0 : ORIGIN = 0x100000, LENGTH = 0x1E00000
ps7_ram_0 : ORIGIN = 0x0, LENGTH = 0x30000
ps7_ram_1 : ORIGIN = 0xFFFF0000, LENGTH = 0xFE00
}
通过修改lscript.ld文件来修改cpu0的内存大小,后期如果出现栈溢出的问题,就可以来到这里将内存改大一点。
第六步、ctrl + s保存,ctrl + b编译
尽量编译一次,避免后面调试的时候出现文件不全的问题。
第一步、再新建一个cpu1的工程。处理器一定要选择cpu1
第二步、为了方便,选择hellow world模板。
第三步、修改.c文件的名称为main.c
第四步、修改main.c文件如下
#include
#include "platform.h"
#include "xil_printf.h"
#define COMM_VAL (*(volatile unsigned long *)(0xFFFF0000))
int main()
{
COMM_VAL=0;
//Disable cache on OCM
Xil_SetTlbAttributes(0xFFFF0000,0x14de2);//关闭cache
while(1)
{
print("Hello World cpu1 \n\r");
COMM_VAL = 0;//这里与CPU0不同,cpu0这里拉高,cpu1这里就需要拉低,这样才能让两个cpu交互起来。
while(COMM_VAL == 0)
{
}
}
return 0;
}
第五步、修改cpu1的代码空间
注意:这里应该是私有ram存储cpu1运行的程序及产生的一些实时文件,只属于cpu1。因此这里的地址范围不可以和cpu0重复。
MEMORY
{
ps7_ddr_0 : ORIGIN = 0x2000000, LENGTH = 0x1F00000
ps7_ram_0 : ORIGIN = 0x0, LENGTH = 0x30000
ps7_ram_1 : ORIGIN = 0xFFFF0000, LENGTH = 0xFE00
}
第六步、ctrl + s保存,ctrl + b编译
尽量编译一次,避免后面调试的时候出现文件不全的问题。
第七步、修改cpu1的bsp代码,将其设置为AMP模式
注意:如果这里不设置,那么生成的bin文件是无法使用的
第八步、在原代码上加上 -DUSE_AMP=1
-mcpu=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard -nostartfiles -g -Wall -Wextra -DUSE_AMP=1
第一步、创建镜像文件
注意:
1、这里要看你鼠标放在那个文件夹上,如果是bsp文件夹,那么去添加的时候就没有文件。
2、但是如果想要顺序正确,那么就要把把鼠标先点击app_cpu0这个文件夹,这样生成镜像文件的顺序就是对的(需手动添加cpu1的elf文件),如果放在app_cpu1,那么就是elf bit cpu1,这样cpu1的顺序就提前了
猜想:对于3核或者更多核,应该也是放在控制核的文件夹上生成bin文件。
第二步、如果默认文件夹,保存bin文件
注意:如果默认系统的文件夹不新建文件夹,那么系统会默认你选择的那个文件下(我选的是app_cpu0)生成一个bootimage文件夹。
第二步、如果新建文件夹,保存bin文件
注意:不能是上一次工程的路径,系统会默认是上一次工程的文件夹。
第三步、添加三个.elf文件,和一个bit流文件。
注意:一定不能是上一次工程的文件夹
注意:顺序一定是下面的顺序1、fsbl.elsf,2、.bit,3、cpu0.elsf,4、cpu1.elsf
在app_cpu0下无法调换顺序
1、amp_fsbl位置
2、bit文件位置
3、app0.elf和app1.elf位置
第四步、生成镜像文件
注意:bin文件复制到SD卡后用不了的可能问题
1、SD卡的电平配置错误,或者说BANK0和BANK1的电平不对(√)
2、四个文件的顺序不对(√)
3、添加文件时,路径错误,因为系统会默认上一次工程的文件路径(√)
4、办卡上的SD启动的拨码开关没有拨过去(√)
5、cpu1没有没有设置为AMP模式,也就是少了 -DUSE_AMP=1这个代码(√)
6、没有关闭cache
7、串口选择错误
8、共享内存的数据越界
第五步、生成的结果
选择cpu0的工程进行仿真
勾选cpu1
关于核间中断的配置代码请参考这篇博客:【ZYNQ入门】第二篇、ZYNQ的中断系统及常用中断初始化程序-CSDN博客
这篇博客是我在学习AMP模式的整理笔记,希望对你有帮助。