请选择 进入手机版 | 继续访问电脑版
[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA_SDK高级篇连载-23基于AMP的双核方案

文档创建者:FPGA课程
浏览次数:198
最后更新:2024-10-11
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 1_SDK应用方案(仅旗舰型号) » 2-SDK高级应用方案
本帖最后由 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 系统框图
18c58f3a200d4fc28c253c0669b3354a.jpg
2.2 编译并导出平台文件
1:单击 Block 文件右键Generate the Output ProductsGlobalGenerate
2:单击 Block文件右键Create a HDL wrapper(生成 HDL顶层文件)Let vivado manager wrapper and auto-update(自动更新)
3:生成 Bit 文件。
4:导出到硬件: FileExport HardwareInclude bitstream
5:导出完成后,对应工程路径的 soc_hw 路径下有硬件平台文件:system_wrapper.xsa 的文件。根据硬件平台文件
system_wrapper.xsa 来创建需要 Platform 平台。
75ce8b77be4343799ddaa4228d1ac3df.jpg
3 搭建 Vitis-sdk 工程
创建 soc_base sdk platform APP 工程。
3.1 创建 SDK Platform 工程
0c5151d57a9343e99a8ed06ca5b65805.jpg
3.2 创建 SDK APP 工程
1:lwip 库的修改
再次提醒用户,首先需要确保 lwip ip 库已经被修改。
新版本系列工业级开发板板载网口芯片是RTL8211FDI,由于默认的驱动不支持,需要手动自己修改库文件。我们这里已经提供了修改好的库,解压到vivado的安装路径下的对应路径下:
89d7d523c72f4afabebd34232a87b5a9.jpg
修改好后,需要关闭vitis-sdk然后重新打开sdk,否则无法识别修改的库
为了创建lwip工程需要先对soc_base中的board support package简称bsp设置lwip库的支持
13487a0be0b94e6a9a862bbaf10e8c72.jpg
2:增加一个 domain
2aac592707d9419da3ea21dd8c59668e.jpg
增加一个core1bsp 工程名自己定义,我们这里为standalone_core1
89a16a70879646a8b3b5dc51055a2154.jpg
创建完成后
729a777920ac4c1ea10e757829ff8ced.jpg
3:修改 CORE1 BSP
327942e9738e4ef5ad6215bf417e5c8b.jpg
CORE1 工程的 bsp 中要增加编译选项“-DUSE_AMP=1”,如下图所示。该编译选项将影响 到 CORE1 工程代
码里中断控制器 SCUGIC 的初始化函数以及 Cache 操作函数的编译,若不增加该 选项,可能会出现 CORE0
CORE1 中断异常和 Cache 一致性维护异常。
bd9f363b56984187b7f3672f56da94a7.jpg
4:修改 FSBL 源码
复制以下代码
  1. #define sev() __asm__("sev")
  2. #define CPU1STARTADR 0xFFFFFFF0
  3. #define CPU1STARTMEM 0x02000000
  4. void StartCpu1(void)
  5. {
  6. #if 1
  7. fsbl_printf(DEBUG_GENERAL,"FSBL: Write the address of the application for CPU 1 to 0xFFFFFFF0\n\r");
  8. Xil_Out32(CPU1STARTADR, CPU1STARTMEM);
  9. dmb(); //waits until write has finished
  10. fsbl_printf(DEBUG_GENERAL,"FSBL: Execute the SEV instruction to cause CPU 1 to wake up and jump to the
  11. application\n\r");
  12. sev();
  13. #endif
  14. }
复制代码

3ac00b6abb42431abe882754a2ce7219.jpg
找到 Load boot image 的位置,把 CPU1 的启动函数,在此调用:
  1. /*
  2. * Load boot image
  3. */
  4. HandoffAddress = LoadBootImage();
  5. fsbl_printf(DEBUG_INFO,"Handoff Address: 0x%08lx\r\n",HandoffAddress);
  6. StartCpu1(); /*add starting cpu1*/
  7. #endif
  8. }
复制代码

735be7e79e584a18b6785afcedf6cb13.jpg
5:编译 soc_base
6:创建 core0 APP
e223ff90f7b84f1b9e5616d5321bad96.jpg
708e1650c9004f21bdfaa34a5dbfd1c9.jpg
7:继续创建 core1 APP
621c16cc69f94bd7873658c91405c345.jpg
8:复制源码
复制 uisrc/06_sdk 中的源码到 core0 APP core1 APP src 路径
0cf8f1e5fe184684a8610a0af20e11c0.jpg
9:地址分配
设置两个 core 的内存地址,2 core 的地址确保不能重叠
单击 core0 app lscript.ld 第一个核的地址 0x00100000~0x01F00000,修改后注意保存
96b733ccac3c4dba87c294a9a58067c7.jpg
单击 core1 app lscript.ld 第二个核的地址 0x02000000~0x01F00000,修改后注意保存
6335c40b71dd4ca987591db7bfb8df57.jpg
10:分别编译 core0 APP core1 APP
4 程序分析
本文实验程序分析主要分析软件中断,以及内存共享部分代码,至于以太网、TTC 定时器等,如果读者不清
楚请认真阅读前面相关的文章。
4.1 软件中断
UG585 Interrupts 部分 7.2.1 章节可以找到关于软件中断(SGI)的说明。简而言之,软件中断就是 CPU 自己产生的中断,可用于触发自身和其他 CPUZYNQ 中共有 16 个软件中断可以使用,对应的中断号为 0~15,如下图 所示。通过软件中断可以实现 CPU 之间的相互通信。本例程使用了编号 1 2 的软件中断。
927da8f3741e43509a94657d3611b738.jpg

以下代码为 core 的中断初始化代码,这里我们只关心软件中断部分
2db8ba51c49b4b019f18fe8a337eb35f.jpg
同样以下代码是 core1 的中断初始化代码,我们也只关心软件中断部分
da228ba01d4444a9b4ce14889115dd0b.jpg
1:中断号
对于 ARM GICv2 中断体系,软件中断号为 0~15 16 个中断,我们这里只需要使用 2 个中断,分别用于
core0 core1 的中断。
174cbf1440174d81b36d1a60b54f911b.jpg
2:软件中断函数初始化
85f0c71d8a8a4478b0864270752c7768.jpg
在这个 Init_Software_Intr 函数中,设置中断的触发方式,中断的回调函数,关联中断号到对应的 CPU,并且使能中断。
6f5da6137483449ab68ecb05493f771d.jpg
3:软件中断函数
core0 或者 core1 的中断产生后,core0 core1 都会通过串口控制台打印自己已经接收到中断了。
b310bbb24c6c485cafb9d6b372801cae.jpg

4:软件中断产生函数
4922e7da4bc3409da153bdfd23e94ad0.jpg
Gen_Software_Intr 函数用于产生软件中断给对应的 CPU
3709c90e1b0e4e19a2035e8efdf69b86.jpg
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 访问 DDRCache 中始终会暂存一小部分(通常是 KB~MB 量级)CPU 最近访问的 DDR 某些地址区域中的数据。因此,在应用程序中对 DDR 进行读或写操作,实际上都是 CPU Cache 进行读或写操作。当 DDR 中某个地址范围内的数据突然被除 CPU 以外的 Master(如 DMA)改变时,若此时Cache 中保存了这些区域的数据,且这些数据在 Cache 中状态为有效时,当 CPU 需要再次读取 DDR 这片区域的数据时, 就不会让 Cache 去读取 DDR 中此区域内最新的数据来更新 Cache,再从 Cache 里读取最新的数据,而是直接从 Cache 中读取原来的旧数据,显然这不是我们所期望的结果。这时,便引入了所谓的 Cache 一致性问题。
ZYNQ 中存在 ICache DCacheICache 用于缓存可执行程序,DCache 用于缓存数据。一般情况下,用于保存可执行程序的 DDR 地址范围不会被除 CPU 以外的对象访问。因此,一般不存在 ICache 的一致性问题。而DCache 在很多应用中却经常会被除 CPU 以外的对象访问,所以存在一致性问题。
ZYNQ 中维护 DCache 一致性的方法为:写入方将数据写入 DDR 对应地址区域后,需将残留在 DCache 中相应地址范围内的数据全部刷入 DDR3 中。读取方在从 DDR 相应地址读取数据之前,需将 DCache DDR 相应地址范围内的数据全部设置为 invalid,然后 CPU 会再次通过 DCache DDR3 中读取该地址范围内最新的数据。
b422f41f95b14376b77dc2f97bbd3349.jpg
1:CORE0 定义共享内存的地址空间
core0 tcp_transmission.c 文件中,定义了以下地址空间分配:
39b4304d0eb74ab7a33298ba42aaf7be.jpg

2:put_data_to_region(file, data_buffer, p->tot_len, region)函数
  1. void put_data_to_region(u8 *src_buffer, u8 *dst_buffer, u32 length, shared_region *p)
  2. {
  3. p->data_length =length;
  4. p->dataload = dst_buffer;
  5. memcpy(p->dataload, src_buffer, length);
  6. Xil_DCacheFlushRange((INTPTR)p, sizeof(shared_region));
  7. Xil_DCacheFlushRange((INTPTR)p->dataload, length);
  8. }
复制代码

typedef struct
{
u32 data_length;
u8* dataload;
}shared_region;
shared_region 结构体变量保存了数据包的长度,以及数据包在内存中的起始地址,这个变量保存到了SHARED_BASE_ADDR 0x08000000内存地址空间。在 core1的程序中也有同样的定义,当 core1接收到 core0发送的中断后,从shared_region 结构体变量来获取数据。
3:CORE1 定义的共享内存
44365c60b50544329e2ef2afe3230db3.jpg
可以看到 shared_region 结构体在 core1 的内存地址分配和 core0 的完全相同。
4:get_data_from_region(data, region)函数获取数据
ab8ab1397e6c418fbf77b0215e19e05e.jpg
在以上代码中,当 core0 发给 core1 的中断产生后,通过 get_data_from_region(data, region)获取到数据。以下代
码中通过共享的 shared_region 变量获取到数据的长度,以及数据所在的内存地址空间。然后把数据复制到
BUFFER_BASE_ADDR 0x12000000 的地址空间。
a3abb4a83f64442a97616da66401df5c.jpg
为了使用 xil_printf 打印数据,需要在数据的末尾补上回车换行字符和补 0
9fe0fb70e8a146f9a147a11a18608df2.jpg
最后 core1 发送一个中断给 core0 通知 core0 已经完成了数据接收。
93b6ae418ede44e5ad51586acf0d13fe.jpg
5 方案演示
5.1 硬件准备 本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
4358e0c7fa094eb8b268143007481202.jpg
5.2 实验结果
1:JTAG 模式下运行双核程序
e6667f14beec4645b1f472c0520fd4b8.jpg
全部勾选 core0 和 core1 的程序,然后单击 apply , 之后单击 run
3732091f5958447cba23b503db0f0ca7.jpg
可以看到 core0 和 core1都运行了
370d3b3c696f4e3c98ae9d8c161e9747.jpg
可以看到运行结果如下:
a7ead44358044285af11b8df02fadde2.jpg
打开网络调试助手,发送一串数据给 core0
3bbfb314674a4807b9012f44061917bf.jpg
可以看到运行结果如下:
6c6d665b1dc3411ebbbf297a80dcc3bc.jpg
2:UBOOT 下运行双核程序
这里必须使用 Create Boot Image 方法产生 boot.bin
e1f1028ab83e45b0a07a831ba2cf5633.jpg
cb12ab13fe07470184e98a14f710a109.jpg
6876867ad5ba4480809c66a7b81d4710.jpg
c0d646898467440a980ff3215ad1020e.jpg
复制到 tf 卡或者烧录到 qpsi flash 测试,一定要注意是以下路径的 BOOT.BIN
注意: MLK-H3-CZ08-7100FC 虽然有 TF 卡接口但是不支持 SD 启动(因为设置了2 片 FLASH , 以支持 7100 大容量程序)
e45e4777a34247efb325d2a5729a5b51.jpg

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则