Cyclone V ACPの実験その1

FPGA

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に接続しています。

ACP動作確認環境

ACP動作確認環境

動作確認

その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
タイトルとURLをコピーしました