Smart ZYNQ(SP&SL 版) 工程三十四 ZYNQ PS 端的双核AMP实验

众所周知,ZYNQ 中包含了双核A9,本节我们就尝试在两个CPU上跑完全独立相互没有交集的程序。并最终把程序下载到FLASH中启动。

这里我们在程序上添加两个EMIO的LED 灯,每个LED灯都由ZYNQ中两个CPU的其中一个独立进行控制,通过观察灯的闪烁就能推算对应的CPU是否在进行工作。 程序比较简单,以下是完整的过程。

一、 新建Vivado工程

过程可以参考前面章节, 芯片型号选择 XC7Z020CLG484-1

二、 创建BLOCK设计

1)IP INTEGRATOR→Create Block Design,在弹出的对话框中输入设计名,最后点击“OK”,如下图所示

2)在右侧的窗口里 ,点击加号,在选择框里搜索ZYNQ,并找到ZYNQ7 PROCESSING SYSTEM ,双击并打开

3)软件自动生成了一个 zynq的block 如下图所示,接下来要做一些相应的设置,双击下图中的ZYNQ核

4)依次在弹窗里找到DDR Configuration→DDR Controller Configuration→DDR3,在Memory Part下拉菜单中根据自己板子上的DDR来选择相应的DDR3,本实验所用到型号:MT41K256M16RE-125,数据位宽选择16bit 最后点击“OK”,如下图所示。

5)增加两路EMIO 用来驱动LED用

6) 因为工程暂时用不到 AXI功能,所以可以先禁用AXI功能

7)点击“Run Block Automation”如下图所示。在弹出的选项中保持默认,点击“OK”,即可完成对ZYNQ7 Processing System的配置

8)将刚才添加EMIO GPIO 引出 右键GPIO_0—->Make External

9)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的指令此图片的alt属性为空;文件名为image-11-1024x340.png

为了让两个CPU同时访问共享内存(OCM)的时候可以数据一致(这样两个CPU就可以相互进行数据的交互了,本节没有用到,如需核间通讯就需要用到),我们需要额外关闭了L1 Cache缓存,防止数据经过缓存后导致数据的不同步。关闭L1 Cache缓存的指令如下:

Xil_SetTlbAttributes(0xFFFF0000,0x14de2);

2)接下来,我们把启动代码添加到 CPU1的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模式后再尝试下载

以下是本节的完整工程:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注