ZYNQ包括一個 FPGA 和兩個 ARM,多個 ARM 核心相對獨立的運行不同的任務,每個核心可能運行不同的操作系統或裸機程序,但是有一個主要核心,用來控制整個系統以及其他從核心的允許。因此我們可以在 CPU0 和 CPU1 中獨立跑不同的應用程序,發揮雙核的非對稱性架構的優勢和性能。
從軟件的角度來看,多核處理器的運行模式主要有三種:
① AMP(非對稱多進程):多個核心相對獨立的運行不同的任務,每個核心可能運行不同的操作系統或裸機程序,但是有一個主要核心,用來控制整個系統以及其它從核心的運行。
② SMP(對稱多進程):一個操作系統同等的管理各個內核,例如 PC 機。
③ BMP(受約束多進程):與 SMP 類似,但開發者可以指定將某個任務僅在某個指定內核上執行默認情況下。
裸機程序 ZYNQ 僅運行一個 CPU,這里主要講解 AMP 模式下,兩個 CPU 同時運行的裸機程序開發方法。
一、核間中斷原理(軟中斷SGI)
軟中斷的 ID 都是從0到15,並且都是上升沿觸發,主要用於核間中斷或者 CPU 自己中斷自己。
中斷函數如下:
XScuGic_SoftwareIntr(&InterruptController, //指向GIC指針 INTC_CPU0, //需要中斷的CPU ID XSCUGIC_SPI_CPU0_MASK); //使能該CPU會接受中斷
二、ARM啟動過程
1.ARM 里有個 ROM,存儲了一段程序,ROM起來后從 SD 卡讀取數據 2.啟動 FSBL(First Boot Loader)第一啟動項(有模板) 3.加載 bit(FPGA配置程序),同時加載 elf(ARM應用程序),如果是操作系統則 elf 替換成 uBoot
三、搭建軟件 CPU0 和 CPU1 非對稱環境
1.創建 amp_fsbl 用於生成燒寫鏡像的時候加載 core0 和 core1的代碼。
①啟動 Vivado,創建 ZYNQ,勾選 SD卡 和 UART 即可,並加載 SDK 開發環境
②進到 SDK ,點擊 File --- New --- Application Project,命名為 amp_fsbl 並選擇 CPU0
③選擇 FSBL,finish
④修改 main.c 函數來啟動 core1,需要兩個步驟,首先把 CPU1 應用程序地址寫入到 0xfffffff0地址中,這是啟動 CPU1 命令的地址,然后執行 sev 指令加載 CPU1 應用程序。具體參考 UG585 的啟動代碼章節。
在 main.c 函數的 main 函數上方插入這段代碼:
1 #define sev() __asm__("sev") 2 #define CPU1STARTADR 0xFFFFFFF0 3 #define CPU1STARTMEM 0x2000000 4 5 void StartCpu1(void) 6 { 7 #if 1 8 fsbl_printf(DEBUG_GENERAL,"FSBL: Write the address of the application for CPU 1 to 0xFFFFFFF0\n\r"); 9 Xil_Out32(CPU1STARTADR, CPU1STARTMEM); 10 dmb(); //waits until write has finished 11 fsbl_printf(DEBUG_GENERAL,"FSBL: Execute the SEV instruction to cause CPU 1 to wake up and jump to the application\n\r"); 12 sev(); 13 #endif 14 }
在 main.c 函數的 load鏡像位置加入代碼: StartCpu1();
2.創建 app_cpu0 應用程序
①重新創建一個新的 File --- New --- Application Project,命名為 app_cpu0,這時選擇的是 CPU0
②選擇 helloworld 模板即可
③將 helloworld.c 改名為 mian.c,並且用如下代碼替換掉內容
1 #include <stdio.h> 2 #include "platform.h" 3 #include "xil_printf.h" 4 5 #define COMM_VAL (*(volatile unsigned long *)(0xFFFF0000)) //無優化無符號長整形地址轉成指針 6 7 int main() 8 { 9 COMM_VAL=0; 10 11 //Disable cache on OCM 12 Xil_SetTlbAttributes(0xFFFF0000,0x14de2); 13 while(1) 14 { 15 print("Hello World cpu0 \n\r"); 16 COMM_VAL =1; 17 while(COMM_VAL == 1) 18 { 19 } 20 } 21 return 0; 22 }
該指針指向的地址為片上存儲的共享內存(OCM)。可以指定這里面的任意一個地址。定義的指針變量如下所示: #define COMM_VAL (*(volatile unsigned long *)(0x00020000)) 該變量CPU0和CPU1都可以在里面讀寫數據,可以達到CPU0和CPU1的數據交互。OCM的地址如下圖所示。
④打開 Scr 目錄中的文件 lscript.ld,點擊下方的 Source 做如下修改:CPU0 的代碼空間改為 1E00000
CPU1運行程序的地址改為fsbl里面指定的起始地址。后面為長度,有一點需要注意:CPU0的起始地址加上CPU0的長度之后不能超過CPU1的起始地址。也就是兩個CPU占用的DDR的地址不能重疊。若程序很大,則CPU0需要的LENGTH就大,因此CPU1的起始地址數值就大。DDR地址范圍如下所示:
3.創建 app_cpu1 應用程序
①重新創建一個新的 File --- New --- Application Project,命名為 app_cpu1,這時選擇的是 CPU1
②選擇 helloworld 模板即可
③將 helloworld.c 改名為 mian.c,並且用如下代碼替換掉內容
1 #include <stdio.h> 2 #include "platform.h" 3 #include "xil_printf.h" 4 5 #define COMM_VAL (*(volatile unsigned long *)(0xFFFF0000)) 6 int main() 7 { 8 //Disable cache on OCM 9 Xil_SetTlbAttributes(0xFFFF0000,0x14de2); 10 while(1) 11 { 12 while(COMM_VAL == 0) 13 { 14 } 15 print("Hello World cpu1 \n\r"); 16 sleep(2); 17 COMM_VAL=0; 18 } 19 return 0; 20 }
④打開 Scr 目錄中的文件 lscript.ld 點擊下方的 Source 做如下修改起始地址改為 0x2000000 長度 0x1F00000
4.增加編譯選項(設置 CPU1 的BSP setting 為 AMP 模式)
app_cpu1_bps --- 右鍵 --- Board Suport Package Setting,點擊 driver --- ps7_cortexa9_0,添加指令
5.讓雙核跑起來
①點擊 app_cpu0 --- 右鍵 --- Debug As --- Debug Configurations...
② 點擊選項卡的 Application,勾選 0 和 1,然后點擊 Debug,yes
③默認停到第一行,連接好串口,選擇APU --- ARM --- #0,單步慢慢走可以看到有信息打印出,#2也是一樣的。如果讓 #0 和 #1 都一直執行,則串口會一直交替打印。
四、軟中斷的注冊和使用
參考資料:
[1]V3學院FPGA教程
[2]何賓, 張艷輝. Xilinx Zynq-7000嵌入式系統設計與實現[M]. 電子工業出版社, 2016.