Kyoto Maker

Software & Hardware Laboratory Found In Kyoto, 2015.

Helioボード: オンチップメモリーにDMA転送で書き込む実験

はじめに

これまでの実験で、Linuxのユーザーランドからオンチップメモリーに読み書きできるようになりました。 今回は、ユーザーランドからではなく、FPGA内部の信号元から書き込む実験をします。 書き込む内容は、単純なカウンタ回路で生成したストリーム信号です。 IPコアとして提供されている、DMAコントローラとAvalon FIFOメモリーを用います。 これらのIPコアと自作のストリーム信号をQsysで合成し、ユーザーランドからDMAコントローラを制御することでDMA転送を実現します。

この記事を読むことで、Direct Memory Access(パソコン用語としても聞いたことがある方も多いと思います)についての理解が深まり、DMAの本質に近づけます。

flickr: perlin flow particle ribbon 1901

全体像

シグナルジェネレータで生成された信号データをオンチップメモリーに書き込みます。ざっくりした流れは以下の通りです。

  1. CPUがDMAコントローラの転送を命令する。
  2. DMAコントローラが転送開始する。
  3. シグナルジェネレータで信号を生成しFIFOに送信する。DMAコントローラはFIFOから読んでオンチップメモリーに書き込む。
  4. 指定した信号長をオンチップメモリーに書き込んだら、DMAコントローラは処理を終了する。
  5. FIFOが満タンになり、シグナルジェネレータが停止する。

ポイントは中間に挿入されているFIFOで、これによりAvalon Streaming SinkからAvalon Memory-Mapped Slaveにインタフェースを変換しています。なぜこの変換が必要かと言うと、DMAコントローラはAvalon Memory-Mapped Slaveとしか接続できないためです。

+-----------------------+
|   Signal Generator    |
+-----------------------+
        ↓↓↓↓ Avalon Streaming Source
        ↓↓↓↓
        ↓↓↓↓ Avalon Streaming Sink
+-----------------------+
|         FIFO          |
+-----------------------+
        ↓↓↓↓ Avalon Memory-Mapped Slave
        ↓↓↓↓
        ↓↓↓↓ Avalon Memory-Mapped Master (Read)
+-----------------------+
|                       |   Avalon Memory-Mapped Slave
|    DMA Controller     | <================== 制御信号
|                       |
+-----------------------+
        ↓↓↓↓ Avalon Memory-Mapped Master (Write)
        ↓↓↓↓
        ↓↓↓↓ Avalon Memory-Mapped Slave
+-----------------------+
|    On-Chip Memory     |
+-----------------------+

シグナルジェネレータ

Avalon Streaming Sourceを実装しています。 生成している信号は単純に0、1、2と増えていくカウンタです。 より正確には、カウントイネーブル付きの同期式カウンタです。 avalonst_source_readyをenable信号としてとらえると、理解できると思います。(本記事の最後の参考情報に書籍をリンクしておきました)

Avalon Interface Specificationsの「5.6 Signal Details」によると、Avalon Streaming Sourceは一番シンプルな構成だと4本のインタフェースで構成できます。

  • valid: Sourceがデータを送信する時にassertする
  • data: Sinkに送信するデータ
  • error: データにエラーが含まれる場合のビットマスク
  • ready: Sinkが受信準備ができたらassertされる

channelはoptionalと書かれているので、今回はとりあえず無視しました。

実装は以下の通りです。

module signal_generator(
  clk,
  reset_n,
  
  avalonst_source_valid,
  avalonst_source_data,
  avalonst_source_error,
  avalonst_source_ready
);

  input            clk;
  input            reset_n;
  
  output           avalonst_source_valid;
  output  [ 31: 0] avalonst_source_data;
  output  [  7: 0] avalonst_source_error;
  input            avalonst_source_ready;

  reg              out_valid;
  reg     [ 31: 0] out_data;
  reg     [  7: 0] out_error;
  
  always @(posedge clk or negedge reset_n) begin
    if (~reset_n) begin
      out_valid <= 1'b0;
      out_data <= 32'b0;
      out_error <= 8'b0;
    end else if (avalonst_source_ready) begin 
      out_valid <= 1'b1;
      out_data <= out_data + 1;
      out_error <= 8'b0;
    end
  end
  
  assign avalonst_source_valid = out_valid;
  assign avalonst_source_data  = out_data;
  assign avalonst_source_error = out_error;

endmodule

これを以前やったようにコンポーネント化して、Qsysで配置します。

Component EditorでAvalon Streaming SouceのData bits per symbolを8から16に変更しておきます。これをやっておかないと、QsysでFIFOFIFOと接続すると以下のエラーが発生します。

Error: soc_system.signal_generator_0.avalon_streaming_source/fifo_0.in: The source has 8 bits per symbol, while the sink has 16.

Avalon FIFO Memory

Avalon FIFO Memoryの仕様書によると、FIFOは以下の4つの構成を取れます。

今回の場合は、前述した通り、Avalon Streaming SinkからAvalon Memory-Mapped Slaveにインタフェースを変更したいので、4つ目の構成を使用することになります。

QsysのIP CatalogからAvalon FIFO Memoryを探してウイザードを開始します。

以下のように設定にします。その他はデフォルトのままです。

  • Status port -> Create status interface for inputを外す
  • Input type: AVALONST_SINK (変更)
  • Output type: AVALONMM_READ (デフォルトのまま)
  • Enable packet data: チェックを外す

一番シンプルな構成にしたかったので、status interfaceとpacket dataのサポートは外しました。 設定項目の変更によってFIFOの信号線がどう変化するか観察すると理解が深まります。 Block DiagramのShow signalsのチェックを入れると信号線も表示できます。

DMAコントローラ

DMAコントローラは、Avalon Memory-Mapped Slaveのcontrol portから制御します。 レジスタマップの仕様の通りにレジスタに必要なデータを書き込むことで、DMA転送できます。今回の場合は、FIFOからオンチップメモリーへの転送になります。

DMAコントローラへの大まかな指示手順は以下の通りです。

  1. コピー元アドレスを書き込む
  2. コピー先アドレスを書き込む
  3. 転送データ長を書き込む
  4. その他制御情報を書き込み、転送を開始する
  5. 転送完了したか確認する

最後の5は、普通は割り込みを使うと思いますが、まだそのやり方が理解できていないので、DMAコントローラに問い合わせます。

QsysのIP CatalogからDMA Controllerを探してウイザードを開始します。設定はデフォルトのままにします。

結線

Qsysで以下のように結線します。dma_0、fifo_0、signal_generator_0が今回追加したインスタンスです。

f:id:fixme:20150208132940p:plain

  • dma_0のcontrol_port_slaveはh2f_axi_masterと結線
  • dma_0のirqはf2h_irq0と結線 (結線するだけで、割り込みは今回使用しません)

後から参照しやすいように、コンポーネントとベースアドレスをまとめると以下のようになります。

コンポーネント ベースアドレス
HPS-to-FPGAブリッジ (h2f_axi_master) 0xC0000000
DMAコントローラ 0x00010000
FIFO 0x0000
オンチップメモリー 0x00000000

以上で回路設計の作業は完了です。コンパイルしてFPGAに書き込みます。

DMA転送の前に

まずは、レジスタマップの仕様を見ながら、各レジスタのアドレスを求めます。

メモリーマップI/Oから操作する際のDMAコントローラのアドレスは、h2f_axi_masterのベースアドレスが0xC0000000、DMAコントローラのベースアドレスが0x00010000なので、0xC0000000 + 0x00010000 = 0xC0010000になります。

レジスタのアドレスは以下のようになります。

レジスタ アドレス
status 0xC0010000
readaddress 0xC0010004
writeaddress 0xC0010008
length 0xC001000C
control 0xC0010018

※アドレス = 0xC0010000 + オフセット * 4

また、メモリーマップI/Oから操作する際のオンチップメモリーのアドレスは、h2f_axi_masterのベースアドレスが0xC0000000、オンチップメモリーのベースアドレスが0x00000000なので、0xC0000000 + 0x00000000 = 0xC0000000になります。

DMA転送してみる

再掲になりますが、DMA転送の手順は以下の4ステップです。

  1. コピー元アドレスを書き込む
  2. コピー先アドレスを書き込む
  3. 転送データ長を書き込む
  4. その他制御情報を書き込み、転送を開始する
  5. 転送完了したか確認する

この通りにDMAコントローラのレジスタを操作することで、DMA転送できます。

では、実際にやってみます。前々回紹介したユーティリティを使ってレジスタを操作していきます。

1. コピー元アドレスを書き込む

0xC0010004がreadaddressレジスタです。 書き込むデータはFIFOの先頭アドレスは0なので0x00000000とします。

root@socfpga:~# ./devmem2 0xC0010004 w 0x00000000

2. コピー先アドレスを書き込む

0xC0010008がwriteaddressレジスタです。 オンチップメモリーのアドレス0xC0000000を指定します。

root@socfpga:~# ./devmem2 0xC0010008 w 0xC0000000

3. 転送データ長を書き込む

0xC001000Cがlengthレジスタです。 データ長は12バイト = 0x0000000Cとしました。

root@socfpga:~# ./devmem2 0xC001000C w 0x0000000C

4. その他制御情報を書き込み、転送を開始する

ここは少しややこしいです。今回の場合は、以下のビットを立てます。

  • 32ビット幅で転送→WORDビットを1に
  • 転送開始→GOビットを1に
  • lengthレジスタが0になったらトランザクションを終了→LEENビットを1に
  • FIFOの先頭アドレスから常に読みたいのでコピー元のアドレスを固定→RCONビットを1に

以上のフラグから、controlレジスタの値を求めると、0x18Cになります。

  • WORD(2) = 22 = 0x4
  • GO(3) = 23 = 0x8
  • LEEN(7) = 27 = 128 = 0x80
  • RCON(8) = 2~8 = 256 = 0x100

= 0x18C

ちなみに、LEEN(7)を立てないと、DMA転送開始後、statusレジスタBUSY(1)が立ったままになり、DMA転送が完了しません。

0xC0010018はcontrolレジスタです。

root@socfpga:~# ./devmem2 0xC0010018 w 0x0000018C

5. 転送完了したか確認する

0xC0010000がstatusレジスタです。 0x11が返りました。 これは、DONE(0)とLEN(4)のフラグが立っているということで、DMA転送が完了し、lengthレジスタが0になったことを示しています。つまり、正常に転送できました。

root@socfpga:~# ./devmem2 0xC0010000 w
/dev/mem opened.
Memory mapped at address 0x76f5e000.
Value at address 0xC0010000 (0x76f5e000): 0x11

オンチップメモリーの内容を確認してみます。0x10000、0x20000、0x30000とカウンタ的に増えているのがわかります。0x1、0x2、0x3にならなかった原因はまだよくわかっていません。

root@socfpga:~# ./devmem2 0xC0000000 w
/dev/mem opened.
Memory mapped at address 0x76fcc000.
Value at address 0xC0000000 (0x76fcc000): 0x10000

root@socfpga:~# ./devmem2 0xC0000004 w
/dev/mem opened.
Memory mapped at address 0x76fb1000.
Value at address 0xC0000004 (0x76fb1004): 0x20000

root@socfpga:~# ./devmem2 0xC0000008 w
/dev/mem opened.
Memory mapped at address 0x76fa5000.
Value at address 0xC0000008 (0x76fa5008): 0x30000

root@socfpga:~# ./devmem2 0xC000000C w
/dev/mem opened.
Memory mapped at address 0x76fc9000.
Value at address 0xC000000C (0x76fc900c): 0x0

ちなみに、同様の手順で再度DMA転送してみると、0x40000、0x50000、0x60000となり、前回の続きから値が取り出せているのがわかります。

root@socfpga:~# ./devmem2 0xC0000000 w
/dev/mem opened.
Memory mapped at address 0x76f21000.
Value at address 0xC0000000 (0x76f21000): 0x40000

root@socfpga:~# ./devmem2 0xC0000004 w
/dev/mem opened.
Memory mapped at address 0x76f94000.
Value at address 0xC0000004 (0x76f94004): 0x50000

root@socfpga:~# ./devmem2 0xC0000008 w
/dev/mem opened.
Memory mapped at address 0x76f2b000.
Value at address 0xC0000008 (0x76f2b008): 0x60000

root@socfpga:~# ./devmem2 0xC000000C w
/dev/mem opened.
Memory mapped at address 0x76f53000.
Value at address 0xC000000C (0x76f5300c): 0x0

参考情報

Interface 2009年1月号 「FPGA評価キットを使ったグラフィック・イコライザの設計製作」からAvalon Streaming InterfaceとAvalon FIFO Memoryの組み合わせのヒントを得ました。以下のCD-ROM書籍で全文検索して見つけました。

シグナルジェネレータで用いたカウントイネーブル付きの同期式カウンタは、以下の書籍を参考にしました。

おわりに

DMAコントローラとAvalon FIFO Memoryは、未経験のコンポーネントで、2つ組み合わせて動作するか心配でしたが、無事動作させることに成功しました。

仕様書を読んで忠実に従えばなんとかなることもわかりました。今回の実験では、以下の仕様書を何度も読み返しました。

ただ、まだ課題があり、割り込みを使ったDMA転送通知まではできていません。また、欲を言うと、オンチップメモリーではなく、Helioボードに実装されているSDRAMに転送したかったりします。これらについても今後実験できればと思っています。