Xilinx 的 Zynq-7000 系列 FPGA 芯片组是 FPGA 市场上最受欢迎的片上系统 (SoC) 选项之一。由于 Xilinx 的集成开发环境 (IDE) Vivado,这是我在 FPGA 领域工作的个人偏好之一(尽管有时这是一种爱恨交织的关系)。Xilinx 的任何 SoC 芯片组(Zynq-7000 或 UltraScale+)的主要特性之一是嵌入到 FPGA 的可编程逻辑中的多个物理处理器。Zynq-7000 系列 FPGA 专门配备了双核 ARM Cortex-A9 处理器。Zynq 处理器的内核能够共享芯片上的资源,例如片上存储器 (OCM)、DDR、UART、通过中断控制分配器 (ICD) 的中断和全局定时器等等。
虽然我已经很快适应了 Xilinx 的新 SDK 工具/平台 Vitis,但我注意到目前还没有大量关于如何在其 SoC FPGA 上同时运行多个处理器的文档。幸运的是,经过几天的探索,我发现这是一个相当简单的过程。
在过去的项目中,我已经介绍了如何在 Vitis 中为仅在 ARM 核心 0 上运行的 Zynq 上的裸机应用程序创建一个新项目,因此我将直接介绍如何为第二个 ARM 核心创建裸机应用程序( ARM 内核 1) 以及如何为其创建单个引导映像。我在这个项目中使用了 Zynqberry,但这个项目中的任何步骤都不一定特定于Zynqberry ,并且适用于任何 Zynq-7000 系列开发板。
在 Vitis 中,就像在其前身 XSDK 中一样,软件设计基于硬件平台。这就是为什么必须在创建应用程序项目之前在 Vitis 中创建平台项目的原因。该平台将硬件设计从从 Vivado 导出的 XSA 文件导入 Vitis,然后有助于创建板级支持包 (BSP)、第一阶段引导加载程序 (FSBL) 等引导组件,最终是裸机应用程序或操作系统。
每个 BSP 只能支持一个裸机应用程序或操作系统。BSP 与裸机应用程序/OS 的这种层次关系在 Vitis 中称为域。因此,由于 ARM 内核 0 的域已经存在于它的 Hello World 裸机应用程序中,因此需要为第二个 ARM 内核 (ARM 1) 创建一个新域。
要创建新域,请打开 platform.xpr,然后在打开的窗口中右键单击带有绿色符号的平台项目名称(不在资源管理器菜单中 - 将出现的唯一选项是“添加领域'):
为新域指定一个所需的名称,然后为操作系统选择独立选项,因为我们将创建一个裸机应用程序。最后,选择第二个 ARM 内核(ps7_cortexa9_1)作为处理器。
这为创建新应用程序项目时选择 ARM 内核 1 作为目标处理器提供了挂钩。
要创建一个在第二个 ARM 内核上运行的新应用程序,请选择 File > New... > Application Project 并为新应用程序指定您想要的名称,然后单击“Next”。
请务必选择从 Vivado 导出的自定义硬件平台(.XSA 文件):
选择为第二个 ARM 内核创建的第二个域。如果您不小心选择了 ARM 内核 0 的域,“下一步”按钮将灰显,并弹出一条警告消息,指出该域已存在应用程序。
现在,我将 Hello World 应用程序模板用于第二个 ARM 内核 (ARM1)。
在 Zynq 芯片中运行两个双核时很容易错过的关键步骤之一是链接描述文件中的 DDR 内存地址不会被修改以将每个应用程序保留在它们自己的内存部分中。
默认情况下,在创建应用程序时,链接器脚本会为应用程序分配相同的基地址,并且其大小由硬件平台表示,板卡上有多少 DDR 可用。如果保持不变,ARM0 和 ARM1 的应用程序都将尝试从 DDR 中的空间地址空间进行操作。这当然不好。
要修改寻址的 DDR,将一个部分分配给 ARM0,另一个分配给 ARM1,请打开每个应用程序的链接描述文件(位于 Explorer > application > src > lscript.ld)并更改顶部 ps7_ddr_0 的值。
为简单起见,我决定将 Zynqberry DDR 的下半部分分配给 ARM0,将上半部分分配给 ARM1(不过请注意,因为这些是裸机应用程序,除了向 UART 打印一条线外几乎什么都不做,它们绝对不需要这么多 DDR 附近的任何地方来操作)。
为了实现这一点,我将 ARM0 的基地址保留为其默认值 (0x100000),但将大小减半为 0xFF800000。我将 ARM1 的基地址设置为 ARM0 的基地址加上 0xFF800000(0x10080000),它的大小也设置为 0xFF800000。
尽管两个 ARM 内核具有相同的处理能力,但仍有一些操作符,例如启动过程,需要其中一个内核作为主内核,另一个内核作为从内核。在 Zynq 中,ARM0 是主机,因此只有它可以访问某些操作和外围设备。ARM0 还将负责在适当的时间点启动从属 ARM1。
为了让 ARM1 上的应用程序知道它在从属处理器上运行并提供适当的挂钩,必须在其 BSP 中设置非对称多处理 (AMP) 编译器标志。
为此,再次打开 platform.xpr 窗口,选择 Modify BSP settings... 并导航到 ps7_cortexa9_1 > extra_compiler_flags,然后将以下内容添加到已经存在的参数的末尾:
-DUSE_AMP=1
现在所有硬件和 BSP 设置都已配置,可以编写实际的应用程序代码。正如我之前提到的,ARM0 负责启动 ARM1。ARM0 必须遵循两个主要步骤才能成功启动 ARM1(如 UG585 中的第 6.1.10 节所述):
同时运行多个处理器时禁用 OCM 中的缓存也很重要,因为它不是处理器之间的可共享资源。
为安全起见,在将 ARM1 的基地址写入 0xFFFFFFF0 后立即执行 ARM 数据内存屏障指令是一种很好的做法,因为在写入指令完成并且内存空间显示为已更新之前,它不允许处理器继续运行。
最后,可以执行 SEV 指令,该指令就像系统中的信标警报一样,告诉所有在场的处理器唤醒并跳转到它们的应用程序。
另一个快速功能:我希望 UART 控制台以交替模式显示来自 ARM0 和 ARM1 的“Hello World”打印输出。因此,我没有尝试硬核睡眠时间来实现这一点,而是使用了一个变量,我将其类型转换到 0xFFFF0000 的共享内存空间中,作为每个 ARM 可以轮询的值,以了解另一个何时完成其打印语句。
ARM0 代码:
#include
#include
#include "xil_io.h"
#include "xil_mmu.h"
#include "platform.h"
#include "xil_printf.h"
#include "xpseudo_asm.h"
#include "xil_exception.h"
#define sev() __asm__("sev")
#define ARM1_STARTADR 0xFFFFFFF0
#define ARM1_BASEADDR 0x10080000
#define COMM_VAL (*(volatile unsigned long *)(0xFFFF0000))
int main()
{
init_platform();
COMM_VAL = 0;
//Disable cache on OCM
// S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(0xFFFF0000,0x14de2);
print("ARM0: writing startaddress for ARM1\n\r");
Xil_Out32(ARM1_STARTADR, ARM1_BASEADDR);
dmb(); //waits until write has finished
print("ARM0: sending the SEV to wake up ARM1\n\r");
sev();
while(1){
print("Hello World - ARM0\n\r");
sleep(1);
COMM_VAL = 1;
while(COMM_VAL == 1){
}
}
cleanup_platform();
return 0;
}
ARM1代码:
#include
#include
#include "xil_io.h"
#include "xil_mmu.h"
#include "platform.h"
#include "xil_cache.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xpseudo_asm.h"
#include "xil_exception.h"
#define COMM_VAL (*(volatile unsigned long *)(0xFFFF0000))
extern u32 MMUTable;
int main()
{
init_platform();
print("CPU1: init_platform\n\r");
//Disable cache on OCM
// S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(0xFFFF0000,0x14de2);
while(1){
while(COMM_VAL == 0){
};
print("Hello World - ARM1\n\r");
sleep(1);
COMM_VAL = 0;
}
cleanup_platform();
return 0;
}
函数 init_platform() 需要保留在两个应用程序中,它负责为每个应用程序初始化 UART 控制台。
构建包含 ARM0 和 ARM1 应用程序的项目系统,然后从 Xilinx 菜单选项中选择“Create Boot Image”。
默认情况下,引导映像写入器将具有 ARM0 的比特流、FSBL 和 ELF 文件的路径。要为 ARM1 添加 ELF 文件,请单击“编辑”并浏览到 ARM1 的 ELF 文件的位置。将类型保留为数据文件,然后单击“确定”。
在“创建引导映像”窗口中,单击“创建映像”。将弹出一个警告,指出以前的版本即将被覆盖。还行吧。
最后,我将 Zynqberry 插入我的计算机并选择 Xilinx 菜单选项下的“Program Flash”选项。Vitis 填写了 FSBL 的位置,并为我填写了启动映像。
正如我的意图,两个内核轮询共享内存值以交替它们的串行输出:
希望这个项目能够帮助您为充分利用您的 Zynq 设计提供一个起点!
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
全部0条评论
快来发表一下你的评论吧 !