众所周知,ZYNQ 中包含了双核A9,本节我们就尝试在两个CPU上跑完全独立相互没有交集的程序。并最终把程序下载到FLASH中启动。
- 此章节内容适用于Smart ZYNQ SP SP2和 SL 版的板子 ( 不包含Smart ZYNQ 标准版 ),如是标准版或本站其他板子请看对应板子目录
- 本文在 vivado2018.3版本上演示
这里我们在程序上添加两个EMIO的LED 灯,每个LED灯都由ZYNQ中两个CPU的其中一个独立进行控制,通过观察灯的闪烁就能推算对应的CPU是否在进行工作。 程序比较简单,以下是完整的过程。
一、工程创建
工程创建的过程可以参考实验一中的内容,这里不详细描述了。基于Smart ZYNQ (SP/SP2/SL 版) 的PS实验一 GPIO之用EMIO方式点亮LED(完整图文) (芯片型号选XC7Z020CLG484-1)
二、Vivado 中的设置
1)在BLOCK DESIGN 中搜索并添加一个ZYNQ模块
2)在ZYNQ 设置界面,设置 DDR MT41K256M16RE-125的型号 和位宽16bit
3)增加两路EMIO 用来驱动LED用
4) 因为工程暂时用不到 AXI功能,所以可以先禁用AXI功能
5)点击“Run Block Automation”如下图所示。在弹出的选项中保持默认,点击“OK”,即可完成对ZYNQ7 Processing System的配置
6)将刚才添加EMIO GPIO 引出 右键GPIO_0—->Make External
7)source→Design Source ,右键我们创建的BLOCK工程,点击create HDL wrapper如下图所示。
在弹出的对话框里保持默认
软件自动为我们生成HDL文件
三、添加管脚约束,并对工程进行编译和综合
1) 点击绿色箭头RUN 对代码进行编译
2) 完成后点击RTL 中的SCHEMATIC , 并选择右边出现的 IO Ports 来增加 EMIO部分的管脚定义(这一步也可以在约束文件中定义, 可看之前的例子)
将2个EMIO的GPIO 分别接P20 P21(保存的时候会让你给约束文件命名,自行定义就好)
3) 生成bit文件 :按下Generate Bitstream 完成综合以及生成bit文件,等待弹出综合完成的窗口
四、SDK部分工程创建
1)File→Export→Export hardware…,在弹出的对话框中勾选“include bitstream”,点击“OK”确认,如下图所示。
2)File→Lauch SDK,在弹出的对话框中,保存默认,点击“OK”,如下图所示。
系统将自动打开SDK开发环境
五、接下去我们要接连创建两个CPU工程
1)新建第一个CPU0的工程 file→new→Application Project,来新建一个“Application Project”,如下图所示。
2)在新建工程名中输入CPU0, 这里一定要选择 PS7_Cortexa9_0
3)选择空工程,点击完成FINISH
4) 在CPU0工程中添加main.c文件 src—>New—>Source File 如下图所示
5)在弹出的窗口中填入main.c 并且保存
6)打开刚才创建的main.c 在其中编写CPU0的代码 ,这里LED1对应 54脚,即EMIO的GPIO0 ,也就是对应LED1。
#include "xparameters.h" #include "xgpiops.h" #include "xstatus.h" #include "xplatform_info.h" #include "sleep.h" #define LED1 54 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID XGpioPs Gpio; void Gpio_Init(void){ XGpioPs_Config *ConfigPtr; ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, LED1, 1); XGpioPs_SetOutputEnablePin(&Gpio, LED1, 1); XGpioPs_WritePin(&Gpio, LED1, 0); } int main(void) { Gpio_Init(); while(1){ XGpioPs_WritePin(&Gpio, LED1, 0); sleep(1); XGpioPs_WritePin(&Gpio, LED1, 1); sleep(1); }; return 0; }
7)分配CPU0的DDR地址: 打开CPU0工程中的lscript.ld文件,将右侧的Size 改成0XFF00000(也就是255MB的DDR空间大小,相当于512MB的一半),之后按下键盘上的ctrl+s进行保存(也可以按SDK软件的保存键进行保存)
8) 再用同样的方式创建第二个CPU工程, file→new→Application Project,来新建一个“Application Project”, 在新建工程名中输入CPU1, 这里一定要选择 PS7_Cortexa9_1(这里和之前CPU0有区别)
9) 用同样的方式在CPU1工程中添加main.c文件 src—>New—>Source File
10)也在刚刚弹出的窗口中填入main.c 并且保存
11)打同样开刚才创建的main.c 在其中编写CPU1的代码 ,这里LED2对应 55脚,即EMIO的GPIO1 ,也就是对应LED2。
#include "xparameters.h" #include "xgpiops.h" #include "xstatus.h" #include "xplatform_info.h" #include "sleep.h" #define LED2 55 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID XGpioPs Gpio; void Gpio_Init(void){ XGpioPs_Config *ConfigPtr; ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, LED2, 1); XGpioPs_SetOutputEnablePin(&Gpio, LED2, 1); XGpioPs_WritePin(&Gpio, LED2, 0); } int main(void) { Gpio_Init(); while(1){ XGpioPs_WritePin(&Gpio, LED2, 0); sleep(2); XGpioPs_WritePin(&Gpio, LED2, 1); sleep(2); }; return 0; }
对比我们刚才创建的两个CPU的程序 ,可以看到两者的程序几乎相同,CPU0控制着LED1的闪烁,闪烁间隔是1S钟(sleep(1)),CPU1控制着LED2的闪烁,闪烁间隔是2S钟 (CPU0和CPU1的闪烁频率是不同的)。
/////////////////CPU0////////// while(1){ XGpioPs_WritePin(&Gpio, LED1, 0); sleep(1); XGpioPs_WritePin(&Gpio, LED1, 1); sleep(1); }; /////////////////CPU1////////// while(1){ XGpioPs_WritePin(&Gpio, LED2, 0); sleep(2); XGpioPs_WritePin(&Gpio, LED2, 1); sleep(2); };
12)用同样的方式为CPU1分配DDR地址: 打开CPU1工程中的lscript.ld文件,将右侧的Size 改成0XFF00000(也就是255MB的DDR空间大小,相当于512MB的一半),这里还要将左侧的基地址修改成0x10000000(这里和之前不一样,要确保CPU1和CPU0的DDR地址不重叠)之后按下键盘上的ctrl+s进行保存(也可以按SDK软件的保存键进行保存)
13)右键 CPU1_bsp ,选择Board Support Package Settings,在extra_compiler_flags 的value 栏末尾加 -DUSE_AMP=1(和前面的要有空格),之后点选OK确认
-DUSE_AMP=1
经过查找资料知道,因为CPU0和CPU1使用相同的BSP,增加 -DUSE_AMP=1 的作用是为了让CPU1运行的时候不再重复对CPU0和CPU1共同使用的资源进行初始化(例如L2 缓存,和全局时钟)。
六 、通过JTAG进行DEBUG
1 )打开 Run->Run Configurations
2)在弹出的窗口中双击Xilinx C/C++application(System Debug)对下载的选项进行设置
3)在System Debugger on Local 的 Target Setup 中,勾选Reset entire system 和 Program FPGA选项(这样每次debug都会自动重载FPGA部分的bit,以及重新初始化整个系统)
4) 在Application 下勾选CPU0 和 CPU1 两个Project(备注 如果Debug/CPU0.elf 或者CPU1.elf没有出现,请提前先对工程进行编译)
5)之后点 Apply,和RUN ,没问题的话 两个LED灯都开始 按不同频率进行闪烁了。证明我们的两个CPU都正常的在运行各自不同的程序了。
七、双核AMP程序 FLASH固化
1)上文中的内容是通过JTAG DEBUG的方式进行,两个CPU可以独立正常工作,但是如果我们要从FLASH 或者SD卡进行上电启动操作,上面的此程序就行不通了,因为系统默认只会启动CPU0, 我们需要在CPU0中加入启动CPU1的代码,这样CPU1也可以正常启动了。
启动代码如下:
#define CPU1_START_MEM 0x10000000 #define sev() __asm__("sev") void Start_Cpu1(){ Xil_Out32(0XFFFFFFF0, CPU1_START_MEM); dmb(); sev(); }
其中Xil_Out32在这里的作用是将启动地址写入到0XFFFFFFF0中,0XFFFFFFF0这个寄存器地址是芯片固定的,CPU1_START_MEM是我们之前设置CPU1的lscript.ld中的DDR起始地址(基地址)0x10000000, 函数最后的sev()是唤醒CPU1的指令
为了让两个CPU同时访问共享内存(OCM)的时候可以数据一致(这样两个CPU就可以相互进行数据的交互了,本节没有用到,如需核间通讯就需要用到),我们需要额外关闭了L1 Cache缓存,防止数据经过缓存后导致数据的不同步。关闭L1 Cache缓存的指令如下:
Xil_SetTlbAttributes(0xFFFF0000,0x14de2);
2)接下来,我们把启动代码添加到 CPU0的main.c中,修改后如下:
//www.hellofpga.com #include "xparameters.h" #include "xgpiops.h" #include "xstatus.h" #include "xplatform_info.h" #include "sleep.h" #include "Xil_mmu.h" #define LED1 54 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID XGpioPs Gpio; void Gpio_Init(void){ XGpioPs_Config *ConfigPtr; ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, LED1, 1); XGpioPs_SetOutputEnablePin(&Gpio, LED1, 1); XGpioPs_WritePin(&Gpio, LED1, 0); } #define CPU1_START_MEM 0x10000000 #define sev() __asm__("sev") void Start_Cpu1(){ Xil_Out32(0XFFFFFFF0, CPU1_START_MEM); dmb(); sev(); } int main(void) { Xil_SetTlbAttributes(0xFFFF0000,0x14de2); Start_Cpu1(); Gpio_Init(); while(1){ XGpioPs_WritePin(&Gpio, LED1, 0); sleep(1); XGpioPs_WritePin(&Gpio, LED1, 1); sleep(1); }; return 0; }
如需要和CPU1进行数据交互,则CPU1也需要关闭L1 Cache缓存,也即增加Xil_SetTlbAttributes(0xFFFF0000,0x14de2);指令(这里用不到就不额外写了)
3) 之后对代码进行保存,并进行重新编译,Build ALL
4)创建FSBL:在SDK中新建工程如下 (FSBL负责启动加载用,之前章节有介绍)
设置工程名fsbl,点next选择FSBL模板
5)制作BOOT.bin用于固化到FLASH。
a) 先选中我们的APP工程目录,这里选择CPU1的目录( 不是FSBL 也不是BSP 和 platform) 如果一开始已经被选中了,请先用鼠标选择上下任意一个目录,再选择回APP目录,否则路径可能不自动加载
b) 点xilinx –>create boot image
在界面里分别设置 boot.bin的输出路径,以及生成boot.bin需要的三种文件的路径(这里有一个偷懒的方法,就是在点下Create Boot Image 之前先选中我们的APP工程),如下图所示,如果没有出现三个文件的路径,请重试上一步。但是选中CPU0工程打开此目录的情况下系统自动添加的默认只有CPU0.elf, 所以这里我们还需要手动添加CPU1.elf文件,路径在SDK\CPU1\Debug\下 , 添加后点create image生成镜像
6) 烧录 镜像:生成完boot.bin后,下一步将boot.bin 下载到QSPI flash 中点Program Flash
按上图操作 导入 boot镜像 文件,和fsbl.elf文件(需要自己导入路径),将flash type 设置为 qspi-x4-sig ,然后将板子拨码开关调整到JTAG模式,并重新上电(或者按下POR RST键),之后再点击PROGRAM 开始下载(必须在JTAG模式下,拨码开关调整到JTAG 模式 并且重启板子或者按下POR RST键)
点击PROGRAM,待程序下载成功后,将跳线位置换回到QSPI上
重新上电(或者按下POR RST键), 正常的话两个两个LED灯都开始 按不同频率进行闪烁了。证明我们的两个CPU都正常的在运行各自不同的程序了(CPU0通过指令成功启动了CPU1)。
如果下载过程中出现下图所示,说明系统上电时没有正常进入JTAG 调试模式,请进入JTAG模式后再尝试下载
- 本文的完整工程下载:12_PS_AMP_TEST
- VIVADO的版本:2018.3
- 工程创建目录:E:\Smart_ZYNQ_SP_SL\SDK\12_PS_AMP_TEST
- 工程适用主板: Smart ZYNQ (SP / SP2 / SL) (不适用于Smart ZYNQ 标准版 )