本帖最后由 FPGA课程 于 2024-10-11 17:29 编辑
软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1 概述
ZYNQ 中存在两个独立的 ARM 核,在很多应用场景中往往只需使用其中的 1 个核心即可。然而,对于复杂的设计,例如多任务,并行控制、处理等,单个核心将难以胜任。因此,为了尽可能发挥 ZYNQ 中双 ARM 核的优势和性能,进行双核应用的开发显得尤为重要。同时,也进一步为 Xilinx 下一代 MPSOC 多核异构处理器的使用打下基础。
在ZYNQ 中实现双 ARM 核 AMP 应用可以参考 Xilinx 官方的 XAPP1078 和 XAPP1079。在 SDK 中也有用于双核应用开发的 openamp 库可以使用。本例程未使用 openamp 库,通过自行设计的代码实现了一个简单的双核应用。
实验目的:
1:掌握双核通过软件中断进行核间通信的原理及方法。
2: 掌握双核通过共享内存进行数据交互的基本原理和设计方法。
3: 熟悉双核协同工作的基本模式。
4:制作双核 BOOT 的方法。
2 系统构架
详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建 SOC 工程的,请学习“3-1-01 米联客2024 版 ZynqSocSDK 入门篇”中第一个工程 “01Vitis Soc 开发入门”这个实验。
2.1 系统框图
2.2 编译并导出平台文件
1:单击 Block 文件→右键→Generate the Output Products→Global→Generate。
2:单击 Block文件→右键→Create a HDL wrapper(生成 HDL顶层文件)→Let vivado manager wrapper and auto-update(自动更新)。
3:生成 Bit 文件。
4:导出到硬件: File→Export Hardware→Include bitstream
5:导出完成后,对应工程路径的 soc_hw 路径下有硬件平台文件:system_wrapper.xsa 的文件。根据硬件平台文件
system_wrapper.xsa 来创建需要 Platform 平台。
3 搭建 Vitis-sdk 工程
创建 soc_base sdk platform 和 APP 工程。
3.1 创建 SDK Platform 工程
3.2 创建 SDK APP 工程
1:lwip 库的修改
再次提醒用户,首先需要确保 lwip ip 库已经被修改。
新版本系列工业级开发板板载网口芯片是RTL8211FDI,由于默认的驱动不支持,需要手动自己修改库文件。我们这里已经提供了修改好的库,解压到vivado的安装路径下的对应路径下:
修改好后,需要关闭vitis-sdk然后重新打开sdk,否则无法识别修改的库
为了创建lwip工程需要先对soc_base中的board support package简称bsp设置lwip库的支持
2:增加一个 domain
增加一个core1的bsp 工程名自己定义,我们这里为standalone_core1
创建完成后
3:修改 CORE1 的 BSP
在 CORE1 工程的 bsp 中要增加编译选项“-DUSE_AMP=1”,如下图所示。该编译选项将影响 到 CORE1 工程代
码里中断控制器 SCUGIC 的初始化函数以及 Cache 操作函数的编译,若不增加该 选项,可能会出现 CORE0 和
CORE1 中断异常和 Cache 一致性维护异常。
4:修改 FSBL 源码
复制以下代码
- #define sev() __asm__("sev")
- #define CPU1STARTADR 0xFFFFFFF0
- #define CPU1STARTMEM 0x02000000
- 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 boot image 的位置,把 CPU1 的启动函数,在此调用:
- /*
- * Load boot image
- */
- HandoffAddress = LoadBootImage();
- fsbl_printf(DEBUG_INFO,"Handoff Address: 0x%08lx\r\n",HandoffAddress);
- StartCpu1(); /*add starting cpu1*/
- #endif
- }
复制代码
5:编译 soc_base
6:创建 core0 APP
7:继续创建 core1 APP
8:复制源码
复制 uisrc/06_sdk 中的源码到 core0 APP 和 core1 APP 的 src 路径
9:地址分配
设置两个 core 的内存地址,2 个 core 的地址确保不能重叠
单击 core0 app 的 lscript.ld 第一个核的地址 0x00100000~0x01F00000,修改后注意保存
单击 core1 app 的 lscript.ld 第二个核的地址 0x02000000~0x01F00000,修改后注意保存
10:分别编译 core0 APP 和 core1 APP
4 程序分析
本文实验程序分析主要分析软件中断,以及内存共享部分代码,至于以太网、TTC 定时器等,如果读者不清
楚请认真阅读前面相关的文章。
4.1 软件中断
在 UG585 的 Interrupts 部分 7.2.1 章节可以找到关于软件中断(SGI)的说明。简而言之,软件中断就是 CPU 自己产生的中断,可用于触发自身和其他 CPU。ZYNQ 中共有 16 个软件中断可以使用,对应的中断号为 0~15,如下图 所示。通过软件中断可以实现 CPU 之间的相互通信。本例程使用了编号 1 和 2 的软件中断。
以下代码为 core 的中断初始化代码,这里我们只关心软件中断部分
同样以下代码是 core1 的中断初始化代码,我们也只关心软件中断部分。
1:中断号
对于 ARM 的 GICv2 中断体系,软件中断号为 0~15 共 16 个中断,我们这里只需要使用 2 个中断,分别用于
core0 和 core1 的中断。
2:软件中断函数初始化
在这个 Init_Software_Intr 函数中,设置中断的触发方式,中断的回调函数,关联中断号到对应的 CPU,并且使能中断。
3:软件中断函数
当 core0 或者 core1 的中断产生后,core0 和 core1 都会通过串口控制台打印自己已经接收到中断了。
4:软件中断产生函数
Gen_Software_Intr 函数用于产生软件中断给对应的 CPU
4.2 共享内存
所谓共享内存就是,CORE0 和 CORE1 在 DDR3 内存中约定一块地址及长度已知的内存区域。然后,两者之间便可通过这片区域进行数据的传递。
两个核心各自拥有独立的 L1 DCache,并且共享同一个 L2 DCache,在 ZYNQ 中存在一个 Snoop Control Unit (SCU)用于维护 CORE0 和 CORE1 的 L1 DCache 与 L2 DCache 之间的一致性,无需用户干预。因此,虽然 CORE0 和 CORE1 的共享内存区域位于 DDR 中,两者之间的数据传递并不需要考虑 DCache 一致性的维护。但是,为了更好阐明多级存储器结构的特性以及 DCache 一致性维护的问题,本例程在两核间通过 DDR3 共享内存进行数据交互时加入了 DCache 一致性操作,最终达到的效果与不使用 DCache 一致性操作时相同。
DCache 一致性维护的原理为:在多级存储器结构中,CPU 通过 1 级或多级 Cache 与 DDR 产生连接,CPU本身不直接访问 DDR,而是通过 Cache 访问 DDR。Cache 中始终会暂存一小部分(通常是 KB~几 MB 量级)CPU 最近访问的 DDR 某些地址区域中的数据。因此,在应用程序中对 DDR 进行读或写操作,实际上都是 CPU 对 Cache 进行读或写操作。当 DDR 中某个地址范围内的数据突然被除 CPU 以外的 Master(如 DMA)改变时,若此时Cache 中保存了这些区域的数据,且这些数据在 Cache 中状态为有效时,当 CPU 需要再次读取 DDR 这片区域的数据时, 就不会让 Cache 去读取 DDR 中此区域内最新的数据来更新 Cache,再从 Cache 里读取最新的数据,而是直接从 Cache 中读取原来的旧数据,显然这不是我们所期望的结果。这时,便引入了所谓的 Cache 一致性问题。
ZYNQ 中存在 ICache 和 DCache,ICache 用于缓存可执行程序,DCache 用于缓存数据。一般情况下,用于保存可执行程序的 DDR 地址范围不会被除 CPU 以外的对象访问。因此,一般不存在 ICache 的一致性问题。而DCache 在很多应用中却经常会被除 CPU 以外的对象访问,所以存在一致性问题。
ZYNQ 中维护 DCache 一致性的方法为:写入方将数据写入 DDR 对应地址区域后,需将残留在 DCache 中相应地址范围内的数据全部刷入 DDR3 中。读取方在从 DDR 相应地址读取数据之前,需将 DCache 中 DDR 相应地址范围内的数据全部设置为 invalid,然后 CPU 会再次通过 DCache 从 DDR3 中读取该地址范围内最新的数据。
1:CORE0 定义共享内存的地址空间
在 core0 的 tcp_transmission.c 文件中,定义了以下地址空间分配:
2:put_data_to_region(file, data_buffer, p->tot_len, region)函数
- void put_data_to_region(u8 *src_buffer, u8 *dst_buffer, u32 length, shared_region *p)
- {
- p->data_length =length;
- p->dataload = dst_buffer;
- memcpy(p->dataload, src_buffer, length);
- Xil_DCacheFlushRange((INTPTR)p, sizeof(shared_region));
- Xil_DCacheFlushRange((INTPTR)p->dataload, length);
- }
复制代码
typedef struct
{
u32 data_length;
u8* dataload;
}shared_region;
shared_region 结构体变量保存了数据包的长度,以及数据包在内存中的起始地址,这个变量保存到了SHARED_BASE_ADDR 0x08000000内存地址空间。在 core1的程序中也有同样的定义,当 core1接收到 core0发送的中断后,从shared_region 结构体变量来获取数据。
3:CORE1 定义的共享内存
可以看到 shared_region 结构体在 core1 的内存地址分配和 core0 的完全相同。
4:get_data_from_region(data, region)函数获取数据
在以上代码中,当 core0 发给 core1 的中断产生后,通过 get_data_from_region(data, region)获取到数据。以下代
码中通过共享的 shared_region 变量获取到数据的长度,以及数据所在的内存地址空间。然后把数据复制到
BUFFER_BASE_ADDR 0x12000000 的地址空间。
为了使用 xil_printf 打印数据,需要在数据的末尾补上回车换行字符和补 0
最后 core1 发送一个中断给 core0 通知 core0 已经完成了数据接收。
5 方案演示
5.1 硬件准备 本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
5.2 实验结果
1:JTAG 模式下运行双核程序
全部勾选 core0 和 core1 的程序,然后单击 apply , 之后单击 run
可以看到 core0 和 core1都运行了
可以看到运行结果如下:
打开网络调试助手,发送一串数据给 core0
可以看到运行结果如下:
2:UBOOT 下运行双核程序
这里必须使用 Create Boot Image 方法产生 boot.bin
复制到 tf 卡或者烧录到 qpsi flash 测试,一定要注意是以下路径的 BOOT.BIN 注意: MLK-H3-CZ08-7100FC 虽然有 TF 卡接口但是不支持 SD 启动(因为设置了2 片 FLASH , 以支持 7100 大容量程序)
|