lynxeyedの電音鍵盤

MBDとFPGAと車載で使うデバイスの備忘録

QuickLogic EOS S3のハードマクロFIFOを使う

EOS S3はPSoC3/5LPと比較すると、アーキテクチャの差があるので単純比較できないものの、およそ3~4.5倍ほどの論理ゲート規模を有します。

それはそうと、ロジック規模だけで優劣をつけてしまうので有れば15年以上前のCPLDと同等になってしまいます。それ以上の価値としてEOS S3の特徴は、小規模組み込みデバイスとしては大きめのRAMが搭載されていてサブモジュールごとに割り振られている点です。Cortex-M4Fには512KB、eFPGAには8kBのRAMが割り振られています。

FPGA側から8KBのRAMはハードマクロRAMまたはFIFOとしてインスタンスできます。8kBはちょっと少ないんじゃないかという感じもしますが、EOS S3はセンサーマネジメントSoCなので、FPGA IPでなんでも作り込むような古典的思想ではなく、飽くまでセンサーIFのみを用意するにとどめ、取得したデータはDMAでCortex-M4F側のSRAMに転送する前提なのでこれはこれでいいのかなと。DMAのDestinationはCortex-M4FサブモジュールのSRAMだけでなくSPIマスタにも切り変え可能なため、IoT RAMのような高速大容量SPI RAMがDestination側で使えたらいいなと考えているところです。

下記の順番でこの記事は構成されています。

使用可能なハードマクロ

FPGAが利用できるハードマクロは

  • RAM (8 x 8Kbits)
  • FIFO (8 x 8Kbits)
  • 乗算器 (32bit x 32bit x 2)

です。小規模FPGAらしく必要最小限でとどめていて好印象。

参考:https://quicklogic-fpga-tool-docs.readthedocs.io/en/latest/ram/S3BDeviceHardmacroResources.html#macro-usage-and-examples

ハードマクロFIFOインスタンス

Design example Using FIFOsにハードマクロFIFOインスタンス事例と、VerilogのexampleコードYour-Installed_Path/quicklogic-arch-defs/tests/fifo_testがあります。Wishbone bus readyなコードになっているので、自作プロジェクト内で動作できるように若干の修正は行いますが、ほぼそのまま使おうとおもいます。CPU側のコードだけ追記するような感じです。
FIFOは8kbit、16kbitのブロックを使用することができ、それぞれのブロックを並列、直列接続することによってビット長を増やしたり、単純にFIFO深さをn倍にしたりすることもできます。

下記例は8kbitのFIFOを1ブロック分インスタンスしています。

(前略)
 FIFO_8K_BLK  # (.data_depth_int(data_depth_int),.data_width_int(data_width_int),.reg_rd_int(reg_rd_int),.sync_fifo_int(sync_fifo_int)
             )
      FIFO_INST  ( .DIN(DIN), .PUSH(PUSH), .POP(POP), .Fifo_Push_Flush(Fifo_Push_Flush), .Fifo_Pop_Flush(Fifo_Pop_Flush), .Push_Clk(Push_Clk),
            .Pop_Clk(Pop_Clk),.PUSH_FLAG(PUSH_FLAG), .POP_FLAG(POP_FLAG), .Push_Clk_En(Push_Clk_En), .Pop_Clk_En(Pop_Clk_En),
            .Fifo_Dir(Fifo_Dir),  .Async_Flush(Async_Flush), .Almost_Full(Almost_Full), .Almost_Empty(Almost_Empty), .DOUT(DOUT));

それぞれの信号の詳細は、FIFO Usage — QuickLogic-FPGA-Toolchainを参照ください。

Cortex-M4F側のプログラム

Exampleでは3つ、タイプの異なるFIFOが用意されています。
メモリマップのとおり、構造体を用意しました。
fpga/inc/fpga_fifoctrl.h

typedef struct fpga_fifoctrl {
    uint32_t    device_id;		// 0x00
    uint32_t    rev_num;		// 0x04
    uint32_t    gpio_in;		// 0x08
    uint32_t    gpio_out;		// 0x0C
    uint32_t    gpio_oe;		// 0x10
    uint32_t    reserved1[60-1];
    uint32_t    fifo1_acc;		// 0x100
    uint32_t    fifo1_flags;		// 0x104
    uint32_t    reserved2[63-1];
    uint32_t    fifo2_acc;		// 0x200
    uint32_t	fifo2_flags;		// 0x204
    uint32_t    reserved3[127-1];
    uint32_t	fifo3_acc;		// 0x400
    uint32_t	fifo3_flags;		// 0x404
} fpga_fifoctrl_t;


fpga_fifoctrl_t* fifoctrl_regs = (fpga_fifoctrl_t*)(FPGA_PERIPH_BASE);

このままだとちょっとアクセスが面倒な感じがするので、それぞれのFIFOやフラグにアクセスできるように関数を用意します。

// FIFO APIs
uint32_t    fpga_getflag(uint8_t ch);
uint32_t    fpga_getfifo(uint8_t ch); 
void        fpga_setfifo(uint8_t ch, uint32_t value);

フラグ取得API,FIFO読み込みAPI,FIFO書き込みAPIです。
main関数で下記の手順を実行するテストコードを書きます。

  1. ライト動作
    1. 特定のChannelのFIFOに深さ512だけ書き込む
  2. リード動作
    1. ステータスを読み込む
    2. FIFOをリードする
    3. 上記2ステップをFIFO深さ512だけ実行する
  3. Channel1~3まで上記ステップを繰り返し


コードです。
src/main.c

    // (中略) 
    // test each FIFOs(FIFO1~3)
    for (uint8_t ch=FIFO_CH1 ; ch<=FIFO_CH3 ; ch++) {
      dbg_str("\r\n\r\n------------------ CHANNEL "); dbg_int(ch); dbg_str(" ------------------");

      for(uint32_t i=0 ; i<512 ; i++) {
        fpga_setfifo(ch,i);
      }
      for(uint32_t i=0 ; i<512 ; i++) {
        dbg_str("\r\nstatus = 0x");
        dbg_hex32(fpga_getflag(ch));
        dbg_str("....fifo = 0x");
        dbg_hex32(fpga_getfifo(ch));
      }

      // 最後のFIFOをReadした後(=FIFOはすべてEmpty)のFlagを確認する  
      dbg_str("\r\nstatus = 0x");
      dbg_hex32(fpga_getflag(ch));
      dbg_str(".\r\n");
      
    }

動作確認

QuickFeatherにUSB-UARTを接続していると動作確認ができます。

------------------ CHANNEL 1 ------------------
status = 0x00000f80....fifo = 0x00000000
status = 0x00000e8f....fifo = 0x00000001
status = 0x00000e0e....fifo = 0x00000002
status = 0x00000e0e....fifo = 0x00000003
status = 0x00000e0e....fifo = 0x00000004

()

status = 0x00008102....fifo = 0x000001ff
status = 0x00008001.

------------------ CHANNEL 2 ------------------
status = 0x00000e03....fifo = 0x00000000
status = 0x00000d02....fifo = 0x00000001
status = 0x00000d02....fifo = 0x00000002
status = 0x00000d02....fifo = 0x00000003
status = 0x00000d02....fifo = 0x00000004

()

status = 0x00000302....fifo = 0x000001fc
status = 0x00000202....fifo = 0x000001fd
status = 0x00000202....fifo = 0x000001fe
status = 0x00008102....fifo = 0x000001ff
status = 0x00008001.


------------------ CHANNEL 3 ------------------
status = 0x00000f80....fifo = 0x00000000
status = 0x00000e8f....fifo = 0x00000001
status = 0x00000e0e....fifo = 0x00000002
status = 0x00000e0e....fifo = 0x00000003

()

status = 0x00000302....fifo = 0x000001fb
status = 0x00000302....fifo = 0x000001fc
status = 0x00000202....fifo = 0x000001fd
status = 0x00000202....fifo = 0x000001fe
status = 0x00008102....fifo = 0x000001ff
status = 0x00008001.

こちらも適宜参照ください

Channel1、FIFOリード動作の初めの部分

深さ512分だけFIFOを書き込んだ後からスタートしています。Status(Flag1)=0x0f80ですので、PUSH,POPフラグともにFullを示しています。
Almost Fullフラグも立っています。

Channel1、FIFOリード動作の終盤部分

Status(Flag1)=0x81xxとなっておりAlmost Emptyフラグが立っていることがわかります。

Channel2、FIFOリード動作の初めの部分

Status(Flag2) = 0x00000e03となっています。
Channel2のみFIFO深さが1024となっていますが、FIFOライトしたのは512分だけなので、PUSH,POPフラグともに「半分かそれ以上埋まっている」「半分かそれ以上空いている」=「半分空いている」を示しています。
Channel3は情報は1と似ているため説明は省略致します。

正しく動作していることを確認できました。

コード全体

QORC-SDKプロジェクト、
qorc-sdk/qf_appsフォルダ内で、以下のプロジェクトをcloneしてください。

github.com

$ git clone -b v0.0.2 https://github.com/panda5mt/qf_wbfpga_pio.git

所感

ハードマクロFIFOを体験できました。今回はCPU側からR/Wしましたが、CPU側からはリードオンリーにして、FPGAに接続したイメージセンサなどから所得したデータをDMAで取得したりするのに使えそうです。