Cyclone Vでは、ACP(Accelerated coherency port)の機能を使うことで、CPUとFPGA内のAXIマスタでメイン・メモリの内容を共有できます。通常、このようなメモリの共有にはキャッシュ・フラッシュが必要になりますが、ACPを利用するとキャッシュ・フラッシュが不要になります。Cyclone VのマニュアルにはACPを有効にするための条件が色々と記載されていますが、実際にはどのようにすればACPが有効になるのかを実機で確認してみました。
動作確認環境
- ターゲット機器: DE0-Nano-SoC
- 開発環境: Quartus II/SoC EDS 15.0
- ソフトウェア: ベアメタルアプリ (gnuコンパイラを使用)
ベアメタルアプリの作成方法はこちらです。
動作確認用デザイン
Cyclone VのFPGA内にAXIのDMACを実装し、設定したメモリ・アドレスからデータをリードします。AXIはFPGA-to-HPS Bridgeに接続しています。
動作確認
その1: MMUとデータ・キャッシュOFFで動作を確認
MMUとデータ・キャッシュがOFFの状態で動作を確認してみます。動作確認用のプログラムは次の通りです。
#include <stdio.h> #include <stdlib.h> #include "pplib/pl_address_table.h" #define DDR_ADRS 0x20000000 #define ACP_ADRS 0x80000000 | DDR_ADRS // FPGAのDMACでデータをリードする関数 unsigned int fpga_mem_read(unsigned int addr){ int stat = 0; PP_VTX_TOP_ADRS = addr; // DMAアドレス設定 PP_VTX_DMA_CTRL = 1; // DMA開始 stat = PP_VTX_DMA_CTRL; while ((stat & 0x100) == 0) { // 終了確認 stat = PP_VTX_DMA_CTRL; } return PP_VTX_DMA_DATA; // 読み出したデータを返す } int main(int argc, char **argv) { int i; unsigned int ref; unsigned int val; printf("ACP test\n"); for (i=0;i<10;i++) { printf("TEST: %d\n",i); ref = i | i << 8 | i << 16 | i << 24; (*(volatile unsigned int *)(DDR_ADRS)) = ref; // メモリにライト printf(" CPU write val %08x\n",(*(volatile unsigned int *)(DDR_ADRS))); val = fpga_mem_read(DDR_ADRS); // FPGAからのリード printf(" DDR read value %x\n",val); if (val != ref) printf(" error\n"); val = fpga_mem_read(ACP_ADRS); // FPGAからのリード(ACP) printf(" ACP read value %x\n",val); if (val != ref) printf(" error\n"); } }
このプログラムは、CPUからメイン・メモリの0x20000000番地に32ビットのデータをライトし、それをFPGAのDMACを使ってリードしています。FPGAからのメイン・メモリのリードは、0x20000000と0xA0000000の2か所で行っています。0x20000000はCPUがライトした同じアクセス、そのアドレスを0x80000000オフセットさせた0xA0000000がACPに割り当てられたアドレスです。pplib/pl_address_table.hはFPGA内DMACのレジスタ設定アドレスを定義しているファイルです。ACPには直接関係しません。
実行結果
0x20000000からの通常リード、0xA0000000からのACPリードの両方でCPUがライトしてデータを確認できました。キャッシュとMMUがOFFの場合にはCPUのライトが直ちにDDRに書き込まれるので、キャッシュ・フラッシュは不要ということなのでしょう。ACPウィンドウからも期待通りのデータがリードできるところが興味深いです。
U-Boot SPL 2013.01.01 (Jan 20 2016 - 07:19:29) BOARD : Altera SOCFPGA Cyclone V Board CLOCK: EOSC1 clock 25000 KHz CLOCK: EOSC2 clock 25000 KHz CLOCK: F2S_SDR_REF clock 0 KHz CLOCK: F2S_PER_REF clock 0 KHz CLOCK: MPU clock 925 MHz CLOCK: DDR clock 400 MHz CLOCK: UART clock 100000 KHz CLOCK: MMC clock 50000 KHz CLOCK: QSPI clock 3613 KHz RESET: WARM SDRAM: Initializing MMR registers SDRAM: Calibrating PHY SEQ.C: Preparing to start memory calibration SEQ.C: CALIBRATION PASSED SDRAM: 1024 MiB ALTERA DWMMC: 0 reading hello-mkimage.bin reading hello-mkimage.bin ACP test TEST: 0 CPU write val 00000000 DDR read value 0 ACP read value 0 TEST: 1 CPU write val 01010101 DDR read value 1010101 ACP read value 1010101 TEST: 2 CPU write val 02020202 DDR read value 2020202 ACP read value 2020202 TEST: 3 CPU write val 03030303 DDR read value 3030303 ACP read value 3030303
その2: MMUとデータ・キャッシュONで動作を確認
MMUとデータ・キャッシュを有効にして動作を確認してみます。動作確認用のプログラムは次の通りです。MMUの設定については以下のページのQuad SPIサンプルを参考にしています。
https://www.altera.com/support/support-resources/design-examples/soc.html
#include <stdio.h> #include <stdlib.h> #include "alt_cache.h" #include "alt_mmu.h" #include "pplib/pl_address_table.h" // Determine size of an array #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) // MMU Page table - 16KB aligned at 16KB boundary static uint32_t __attribute__ ((aligned (0x4000))) alt_pt_storage[4096]; static void * alt_pt_alloc(const size_t size, void * context) { return context; } ALT_STATUS_CODE alt_pt_init(void) { // Populate the page table with sections (1 MiB regions). ALT_MMU_MEM_REGION_t regions[] = { // Memory area: 1 GiB { .va = (void *)0x00000000, .pa = (void *)0x00000000, .size = 0x40000000, .access = ALT_MMU_AP_PRIV_ACCESS, .attributes = ALT_MMU_ATTR_WBA, .shareable = ALT_MMU_TTB_S_NON_SHAREABLE, .execute = ALT_MMU_TTB_XN_DISABLE, .security = ALT_MMU_TTB_NS_SECURE }, // Device area: Everything else { .va = (void *)0x40000000, .pa = (void *)0x40000000, .size = 0xc0000000, .access = ALT_MMU_AP_PRIV_ACCESS, .attributes = ALT_MMU_ATTR_DEVICE_NS, .shareable = ALT_MMU_TTB_S_NON_SHAREABLE, .execute = ALT_MMU_TTB_XN_ENABLE, .security = ALT_MMU_TTB_NS_SECURE } }; ALT_STATUS_CODE status = ALT_E_SUCCESS; uint32_t * ttb1 = NULL; if (status == ALT_E_SUCCESS) { status = alt_mmu_init(); } if (status == ALT_E_SUCCESS) { size_t reqsize = alt_mmu_va_space_storage_required(regions, ARRAY_COUNT(regions)); if (reqsize > sizeof(alt_pt_storage)) { status = ALT_E_ERROR; } } if (status == ALT_E_SUCCESS) { status = alt_mmu_va_space_create(&ttb1, regions, ARRAY_COUNT(regions), alt_pt_alloc, alt_pt_storage); } if (status == ALT_E_SUCCESS) { status = alt_mmu_va_space_enable(ttb1); } return status; } ALT_STATUS_CODE alt_pt_uninit(void) { if (alt_mmu_disable() != ALT_E_SUCCESS) { printf("DEBUG[PT]: Failure on line %d.\n", __LINE__); } return ALT_E_SUCCESS; } #define DDR_ADRS 0x20000000 #define ACP_ADRS 0x80000000 | DDR_ADRS unsigned int fpga_mem_read(unsigned int addr){ int stat = 0; PP_VTX_TOP_ADRS = addr; PP_VTX_DMA_CTRL = 1; // DMA開始 stat = PP_VTX_DMA_CTRL; while ((stat & 0x100) == 0) { // 終了確認 stat = PP_VTX_DMA_CTRL; } return PP_VTX_DMA_DATA; } int main(int argc, char **argv) { int i; unsigned int ref; unsigned int val; printf("ACP test\n"); alt_pt_init(); // MMUを設定 alt_cache_system_enable(); // キャッシュを有効化 for (i=0;i<10;i++) { printf("TEST: %d\n",i); ref = i | i << 8 | i << 16 | i << 24; (*(volatile unsigned int *)(DDR_ADRS)) = ref; printf(" CPU write val %08x\n",(*(volatile unsigned int *)(DDR_ADRS))); val = fpga_mem_read(DDR_ADRS); printf(" DDR read value %x\n",val); if (val != ref) printf(" error\n"); val = fpga_mem_read(ACP_ADRS); printf(" ACP read value %x\n",val); if (val != ref) printf(" error\n"); } }
実行結果
予想通り、データの不一致が起こりました。0x20000000からの通常リード、0xA0000000からのACPリードの両方がCPUのライト・データと一致しませんでした。CPUがライトしたデータはキャッシュに留まっているため、DDRの内容は更新されないということなのでしょう。このテスト環境があれば、どのような設定を行えばACPが有効になるのか確認できます。ACPが有効になれば、0xA0000000のACPウィンドウからのリードは、CPUがライトしたデータと一致するはずです。(続く)
ACP test TEST: 0 CPU write val 00000000 DDR read value 9090909 error ACP read value 9090909 error TEST: 1 CPU write val 01010101 DDR read value 9090909 error ACP read value 9090909 error TEST: 2 CPU write val 02020202 DDR read value 9090909 error ACP read value 9090909 error TEST: 3 CPU write val 03030303 DDR read value 9090909 error ACP read value 9090909 error