Kyoto Maker

Software & Hardware Laboratory Found In Kyoto, 2015.

ModelSim Altera Starter Editionが起動できない問題のパッチ

はじめに

Quartus II 14.1 Web Editionをインストールした時に、ModelSim Altera Starter Editionも合わせてインストールしたのですが、ModelSimが起動できない現象が発生して使えない状態が続いてました。これを解決しないことにはシミュレーションができないので、時間を取って解決方法を探しました。期待する答えが中々見つからず、思いのほか苦労しました。

今回は、この問題を解決するパッチを見つけたので紹介します。

flickr: Rainbow Bridge Tokyo

現象

ModelSim Altera Starter Editionを起動しようとすると、以下のダイアログが表示されて起動できません。

f:id:fixme:20150214223604p:plain

解決

こちらにパッチがあるので適用します。 これでModelSimが起動できるようになります。

Due to a problem in the Quartus® II software version 14.1, you are prompted for a license for the full edition license when you invoke Modelsim-Altera Starter edition. This is not the intended behavior.

Quartus II 14.1の問題で、これは意図した挙動ではない、とのこと。

おわりに

賞味期限の早い情報ですが、最近FPGAに入門して同じ現象ではまったことがある人がいるかもしれないので紹介しました。

Helioボード: LinuxとFPGAでSDRAMをシェアする

はじめに

Helioボードには1GBのDDR3-SDRAMが実装されています。 デフォルトの設定ではLinuxが1GBすべてを使用します。 ただ、そうすると、FPGAからSDRAMを読み書きしようとした場合、Linuxが同じメモリー領域にアクセスされると困ります。

そこで、今回はLinuxが使用するSDRAMのメモリー領域を実装メモリーの半分に制限して、OSから邪魔されずFPGAが独占的に使用できるSDRAM領域を確保してみます。動作確認として、FPGA側とLinuxのユーザーランドでSDRAMを読み書きしてみます。

flickr: Sharing Flavored, Colored Ice

変更手順

U-Bootのパラメータを変更することでLinuxが認識するメモリーサイズを変更できます。 具体的には、mmcbootで定義されているbootargsに「mem=<サイズ>M」を追加します。 今回は、1GBの半分、512MBを認識させることにします。

では、Helioボードを起動します。

U-Bootのカウントダウンが始まるので、任意のキーを入力します。

すると、以下のプロントが表示されます。

SOCFPGA_CYCLONE5 #

まず、既存の設定を確認します。ボードのリビジョンによって異なる場合があるかもしれないので、mmcbootの行をメモしておきます。

SOCFPGA_CYCLONE5 # printenv
[...]
mmcboot=setenv bootargs console=ttyS0,115200 root=${mmcroot} rw rootwait;bootz ${loadaddr} - ${fdtaddr}

他の項目は変えず、「mem=512M」を追加します。

SOCFPGA_CYCLONE5 # editenv mmcboot
setenv bootargs mem=512M console=ttyS0,115200 root=${mmcroot} rw rootwait;bootz ${loadaddr} - ${fdtaddr}mmcboot=setenv bootargs console=ttyS0,115200 root=${mmcroot} rw rootwait;bootz ${loadaddr} - ${fdtaddr}

設定できたことを確認します。

SOCFPGA_CYCLONE5 # printenv
[...]
mmcboot=setenv bootargs mem=512M console=ttyS0,115200 root=${mmcroot} rw rootwait;bootz ${loadaddr} - ${fdtaddr}

ここでsaveenvすることで設定保存できますが、はじめての場合は不安なのでこのままLinuxを起動します。設定保存していないので、Linuxを再起動したら設定はデフォルトに戻ります。

SOCFPGA_CYCLONE5 # boot

Linux起動後、メモリーサイズを確認します。確かに512MBとして認識するようになりました。

root@socfpga:~# cat /proc/meminfo | grep MemTotal
MemTotal:         512812 kB

期待通りメモリーサイズを制限できたら、もう一度同じ手順を繰り返し、bootする前にsaveenvコマンドで設定を保存しておきます。

動作確認

以前紹介したQuartus II付属のシステムコンソールを使うことでFPGAから来た信号として代替できます。 FPGA側からSDRAMの512MB目(0x20000000)に0xDEADBEEFを書き込んでみます。

% set m0 [lindex [get_service_paths master] 0]
% open_service master $m0
% master_write_32 $m0 0x20000000 0xDEADBEEF

補足すると、$m0は、リファレンスデザインで実装されている、f2sdram_only_masterインスタンスのサービスパスです。 f2sdram_only_masterは、hps_0のf2h_sdram0_dataポートと接続されていて、JTAG経由で読み書きできるように設計されています。 ですので、$m0を通してSDRAMを読み書きできます。

Linuxからは、以前紹介したユーティリティで書き込んだ値が読み込めるか確認します。正しく読めました。

root@socfpga:~# ./devmem2 0x20000000 w
/dev/mem opened.
Memory mapped at address 0x76f0e000.
Value at address 0x20000000 (0x76f0e000): 0xDEADBEEF

おわりに

LinuxFPGASDRAMをシェアできるようになりました。 ただし、SoCでこの手法が一般的かは不明です^^; 次回は、FPGAで生成した信号をSDRAMに書き込む実験ができればと思います。

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に転送したかったりします。これらについても今後実験できればと思っています。

Helioボード: システムコンソール入門

はじめに

Quartus II付属のシステムコンソールを用いると、FPGA側のLEDなどのペリフェラルに対して簡単に状態取得や制御ができます。(今日知りました^^;) システムコンソールは、SoCボードのLinuxを停止した状態でも使えるため、回路単体でのデバッグに重宝しそうです。今回は、システムコンソールからLチカ、プッシュボタンの状態取得、オンチップメモリーの読み書きを試してみます。

flickr: Arashiyama Bamboo Forest in Sagano, Kyoto, Japan

システムコンソールとは

Quartus IIに付属するコマンドラインツールです。Tcl言語のコマンドを専用コンソールに入力することで、FPGAボードと対話的にやりとりできます。これにより、デバッグの効率を上げることができます。裏ではJTAGを通して通信が行われます。

準備

HelioボードのJTAGポートとPCをUSBケーブルで接続します。

Quartus IIのProgrammerでFPGAに回路を転送します。

Quartus II -> Tools -> System Debugging Tools -> System Consoleを選択します。

Tcl Consoleに以下のコマンドを入力します。

% get_service_paths master
/devices/5CSE(BA5|MA5)|5CSTFD5D5|..@2#USB-1#Helio/(link)/JTAG/sldfabric.node_1/phy_0/f2sdram_only_master.master
/devices/5CSE(BA5|MA5)|5CSTFD5D5|..@2#USB-1#Helio/(link)/JTAG/sldfabric.node_2/phy_1/fpga_only_master.master
/devices/5CSE(BA5|MA5)|5CSTFD5D5|..@2#USB-1#Helio/(link)/JTAG/sldfabric.node_3/phy_2/hps_only_master.master

列挙されたパスは、JTAG to Avalon Master Bridgeと呼ばれる種類のコンポーネントで、Qsysからその存在を確認できます。Qsysでfpga_only_masterインスタンスのmasterポートからの結線先を確認すると、

  • sysid_qsys
  • jtag_uart
  • button_pio
  • dispsw_pio
  • led_pio
  • onchip_memory2_0
  • intr_capturer_0

に接続されています。これらは、すべてFPGA側のペリフェラルで(fpga_only_masterというネーミングはそれを表している)、Avalon Memory Mapped Slaveのポートに接続されています。と言うことは、fpga_only_masterから、これらのAvalon Memory Mapped Slaveに対して読み書きしたら状態取得や制御ができそうです。と、予想しました。実際、当たっていました。

まずは、後から参照しやすいように、fpga_only_masterのパスをm1という変数に格納します。

% set m1 [lindex [get_service_paths master] 1]
/devices/5CSE(BA5|MA5)|5CSTFD5D5|..@2#USB-1#Helio/(link)/JTAG/sldfabric.node_2/phy_1/fpga_only_master.master

fpga_only_masterをオープンします。

% open_service master $m1

最後に、Linuxをシャットダウンしておきます。バックグラウンドで動作しているLEDの点灯プログラムが邪魔なのと、Linuxが停止した状態でシステムコンソールからの状態取得・制御ができることを確認するためです。

root@socfpga:~# shutdown -h now

これで準備が整いました。

Lチカ

Helioにはled_pioから制御できるLEDが4つ実装されています。これらのLEDを制御してみます。

led_pioのベースアドレスは0x10040です。このアドレスに対して書き込めばLEDを点灯消灯できます。

では、システムコンソールで試していきます。

LEDをすべて点灯。負論理なので、0で点灯です。

% master_write_32 $m1 0x10040 0x0

LEDをすべて消灯。

% master_write_32 $m1 0x10040 0xf

LEDを左から順番に1つずつ点灯。負論理でややこしいので、exprでbit反転を使って読みやすくしてみました。イミディエイト値で入力するよりずっと読みやすいですね。

% master_write_32 $m1 0x10040 [expr ~0b0001]
% master_write_32 $m1 0x10040 [expr ~0b0010]
% master_write_32 $m1 0x10040 [expr ~0b0100]
% master_write_32 $m1 0x10040 [expr ~0b1000]

プッシュボタンの状態取得

button_pioが認識するプッシュボタンは2つです。プッシュボタンは3つ実装されていますが、1番右のボタンは死んでいます

プッシュボタンのベースアドレスは0x100c0です。このアドレスに対して読み込めばプッシュボタンの状態を取得できます。

プッシュボタンもLEDと同様に負論理です。

2つとも押していない状態で取得すると、0x3 = 0b11が返ります。

% master_read_32 $m1 0x100c0 1
0x00000003

左側のボタンを押した状態で取得すると、0x2 = 0b10が返ります。

% master_read_32 $m1 0x100c0 1
0x00000002

右側のボタンを押した状態で取得すると、0x01 = 0b01が返ります。

% master_read_32 $m1 0x100c0 1
0x00000001

両方のボタンを押した状態で取得すると、0x00が返ります。

% master_read_32 $m1 0x100c0 1
0x00000000

オンチップメモリーの読み書き

オンチップメモリー(onchip_memory2_0)のベースアドレスは0です。

0x12345678を書き込んでみます。

% master_write_32 $m1 0x0 0x12345678

書き込んだ値を取得します。取得できました。

% master_read_32 $m1 0x0 1
0x12345678

後始末

作業が終わったら、クローズしておきます。

% close_service master $m1

参考情報

システムコンソールの使い方は、以下のページの解説が参考になりました。

おわりに

ネットで調べ物をしていたら、システムコンソールの存在を知り、急遽実験してみました。

これまでリファレンスデザインに実装されている、JTAG to Avalon Master Bridgeコンポーネント郡の役割がよく分かっていませんでしたが、今回の実験で理解できました。

Helioボード: ユーザーランドからFPGA側のオンチップメモリーを読み書きする実験

はじめに

Helioボードのリファレンスデザインには、FPGA側にオンチップメモリーがすでに実装されています。 今回は、ユーザーランドからこのオンチップメモリーに読み書きしてみます。 ただし、Cでコーディングはせず、コマンドから任意のアドレスに対して読み書きできるユーティリティを導入して試みます。

このユーティリティを応用すれば、例えば、FPGAでDMAコントローラを用いたデバイスを作成した場合に、まだデバイスドライバが用意していない状況でも、DMAコントローラのレジスタを直接操作して動作確認する、といったことが可能になります。

flickr: Buildings

メモリー読み書きユーティリティ

こちらで紹介されている、コマンドから任意のアドレスに対して読み書きできるユーティリティーを導入します。

このユーティリティでやっていることは、以前System IDを読み取るためにCで書いたコードと本質的には同じで、/dev/memを使ってメモリーマップで読み書きしているだけです。

以前構築したクロスコンパイル環境を使ってビルドします。

$ wget http://download.atmark-techno.com/misc/accessing-any-address/devmem2.tar.gz
$ tar zxvf devmem2.tar.gz
$ cd devmem2
$ /opt/altera-linux/linaro/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-gcc devmem2.c -o devmem2

ビルドが成功すると、gccに-oオプションで指定した、devmem2という実行ファイルが作成されます。 これをHelioボードのrootユーザーのホームディレクトリにコピーします。

オンチップメモリーの読み書き

オンチップメモリー(onchip_memory2_0)は、Qsysで確認すると、h2f_axi_masterピンと接続されています。h2f_axi_masterピンは、HPS-to-FPGA Bridgeと接続されています。つまり、図にすると以下のようになります。

                              h2f_axi_master
HPS <===> HPS-to-FPGA Bridge <==============> オンチップメモリー (FPGA slaves)

Cyclone V Hard Processor System Technical Reference Manualの「Table 1-2: Common Address Space Regions」を確認すると「FPGA slaves」のベースアドレスは0xC0000000とあります。

f:id:fixme:20150113222925p:plain

オンチップメモリーのベースアドレスは、0x0000_0000なので、オンチップメモリーの先頭アドレスは、FPGA slavesのベースアドレス(0xC0000000) + 0x0 = 0xC0000000になります。

実際に書き込んでみます。第1引数にアドレス、第2引数にデータ長(w = word = 32bit)、第3引数に書き込むデータを指定します。

root@socfpga:~# ./devmem2 0xC0000000 w 0x12345678
/dev/mem opened.
Memory mapped at address 0x76f1a000.
Value at address 0xC0000000 (0x76f1a000): 0x34567800
Written 0x12345678; readback 0x12345678

書き込んだ値を確認します。書き込めていることを確認できました。

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

おまけ: System IDの読み取り

以前はCでプログラムを書いてSystem IDを読み取りました。このユーティリティは任意のアドレスを読み書きできるので、System IDを読み取ることもできます。

Lightweight FPGA slavesのベースアドレスは0xFF200000で、sysid_qsysのベースアドレスは、0x0001_0000です。従って、System IDの先頭アドレスは、0xFF200000 + 0x0001_0000 = 0xff210000になります。

f:id:fixme:20150113222925p:plain

実際にやってみると、System IDが読み取れました。

root@socfpga:~# ./devmem2 0xff210000 w
/dev/mem opened.
Memory mapped at address 0x76f15000.
Value at address 0xFF210000 (0x76f15000): 0xACD51402

おわりに

ユーザーランドからリファレンスデザインで実装されているオンチップメモリーを読み書きできるようになりました。 今回導入したユーティリティは、コマンドから任意のアドレスを読み書きできるので、ちょっとした実験をするのに便利でした。 次回は、FPGAで記述した回路からオンチップメモリーに書き込む実験を紹介予定です。

Helioボード搭載のAltera Cyclone V SoCのスペックの調べ方

はじめに

今回は、Altera Cyclone V SoCの型番からスペックを調べる方法を紹介します。また、型番を知っていると役に立つ例として、Cyclone V SoCに実装されているオンチップメモリーの容量を型番から調べてみます。

flickr: Daily Disney (Explored)

調べ方

所有してるHelioボードのリビジョンを確認します。

確認するとリビジョンが1.4でした。

こちらにあるSchematicを確認すると、2ページ目の「FPGA Package Top View」に「Cyclone V - 5CSXFC5C6U23C7」との記述が見つかります。これが型番です。

Cyclone V Device Overviewに型番の各桁の意味とスペックが記載されています。

はじめの「5C」の次の「SX」がデバイスファミリーを表しています。そこで、「Cyclone V SX」のセクションを探します。すると、「Figure 5: Sample Ordering Code and Available Options for Cyclone V SX Devices」という図が見つかります。

先ほど調べた型番「5CSXFC5C6U23C7」をFigure 5の説明に従って読み解くと以下の表のようになります。例えば、Member Codeを見ると、ロジックエレメントの規模は85Kということが読み取れます。

項目 スペック
5C Family Signature Cyclone V
SX Family Variant SoC with 3-Gbps transceivers
F Embedded Hard IPs Maximum 2 hard PCIe controllers and 1 hard memory controller
C5 Member Code 85K logic elements
C Transceiver Count 6
6 Transceiver Speed Grade 3.125 Gbps
U Package Type Ultra FineLine BGA (UBGA)
23 Package Code 672 pins
C Operating Temperature Commercial (TJ = 0° C to 85° C)
7 FPGA Fabric Speed Grade 7 (6が最速)

応用: オンチップメモリーの容量を調べる

Cyclone V SoCには、オンチップメモリーが実装されています。どれだけの容量があるのでしょうか?

ここで型番が役に立ちます。型番を調べた時に見たCyclone V Device Overviewを開いて、「Embedded Memory Blocks」を見ます。すると、「Table 18: Embedded Memory Capacity and Distribution in Cyclone V Devices」にデバイスファミリーごとのメモリー容量が記載されています。

先ほど調べた通り、デバイスファミリーは「Cyclone V SX」でMember Codeは「C5」なので、対応する列をこの表から読み取ります。

そうすると、M10K (大容量の10KBit)とMLAB(小容量の640KBit)の2種類のメモリーが実装されていて、それぞれ、M10Kは、ブロック数が397で容量は計3970KBit、MLABはブロック数が768で容量は計480KBit、すべて合算すると4450KBit(約560KB)ということがわかります。

ちなみに、Helioのリファレンスデザインでは、64KBのオンチップメモリーがインスタンス化されています。(Qsysで確認できます。「onchip_memory2_0」がそれです) オンチップメモリーのトータル容量は、先ほど調べた通り、560KBなので、8分の1をすでに割り当て済みということになります。逆に言うと、8分の7は、自由に利用できる余地があるということです。実際、このオンチップメモリーの残りの容量を使用して、独自にROMを実装できました。(詳細はまた今度紹介できればと思います)

おわりに

Helioボードを買った時に、まずCyclone V SoCの型番を調べたのですが、その調べ方を記録に残していなかったので、今回文章に起こしました。

Helioボード: プッシュボタンの内部バス幅を2→3ビットに拡張する

はじめに

Helioボードのリファレンスデザインには、FPGA側のプッシュボタンが押されたらコンソールにそれを通知するサンプルが同梱されています。しかし、実は、Helioボードに実装されている3つのプッシュボタンのうち1つが、このサンプルでは反応してくれません。今回は、その原因を究明し、解決してみます。

今回のハイライト: Quartus IIのRTL ViewerでFPAG内部の結線状況を確認している様子です。これから赤線の一部のバス幅を2ビットから3ビットに拡張します。

f:id:fixme:20150124151540p:plain

プッシュボタンの割り込み検出サンプル

HelioボードのLinuxを起動して、rootユーザーでログインすると、ホームディレクトリにREADMEというファイルがあります。 このREADMEにリファレンスデザインで提供されている各種サンプルの動かし方が説明されています。

root@socfpga:~# cat README
Table of Contents
=================
1. Soft IP Driver Example
2. Application Examples


1. Soft IP Driver Example
=========================
GPIO driver for soft PIO in the FPGA domain serves as a reference for
writing a simple driver in Linux. The source code of this driver is located in
...

今回は試すサンプルは、「Application to register interrupt and write simple interrupt service routine」のセクションに書かれているものです。

READMEに従い、GPIOを確認してみると、以下が認識されています。

root@socfpga:~# ls -l /sys/class/gpio/
--w-------    1 root     root          4096 Jan 17 22:10 export
lrwxrwxrwx    1 root     root             0 Jan 17 22:10 gpiochip149 -> ../../devices/virtual/gpio/gpiochip149
lrwxrwxrwx    1 root     root             0 Jan 17 22:10 gpiochip152 -> ../../devices/virtual/gpio/gpiochip152
lrwxrwxrwx    1 root     root             0 Jan 17 22:10 gpiochip156 -> ../../devices/virtual/gpio/gpiochip156
lrwxrwxrwx    1 root     root             0 Jan 17 22:10 gpiochip160 -> ../../devices/virtual/gpio/gpiochip160
lrwxrwxrwx    1 root     root             0 Jan 17 22:10 gpiochip192 -> ../../devices/virtual/gpio/gpiochip192
lrwxrwxrwx    1 root     root             0 Jan 17 22:10 gpiochip224 -> ../../devices/virtual/gpio/gpiochip224
--w-------    1 root     root          4096 Jan 17 22:10 unexport

試しにgpiochip149のlabelを確認してみます。「gpio@0x1000100C0」は、button_pioモジュールのBaseアドレス(Qsysで確認できます)に一致しています。つまり、このgpiochip149がFPGA側のプッシュボタンに対応しています。

root@socfpga:~/altera# cat /sys/class/gpio/gpiochip149/label
/sopc@0/bridge@0xc0000000/gpio@0x1000100C0

READMEの説明に従い、gpio_interruptモジュールをカーネルにロードします。先ほどGPIOの149番がプッシュボタンに対応しているとわかったので、gpio_numberには149を指定します。

root@socfpga:~# modprobe gpio_interrupt gpio_number=149

プッシュボタン(SW-11)を押してみます。すると、ターミナルに「Interrupt happened at gpio:149」と表示されました。

同様に、2つ目のプッシュボタン(SW-12)でも試してみます。READMEの説明にあるように、gpio_interruptをアンロードしてから、再度モジュールをロードします。ここではgpio_numberに149+1=150を指定します。これも動作しました。

root@socfpga:~# rmmod gpio_interrupt
root@socfpga:~# modprobe gpio_interrupt gpio_number=150

この調子で、3つ目のプッシュボタン(SW-13)を試してみると、残念ながら、何も応答がありません。

原因の究明

Qsysでbutton_pioの設定を確認してみます。すると、パラレルI/OのWidthが2ビットになっていることに気づきます。プッシュボタンは3つあるのに、Widthが2ビットということは、3つあるプッシュボタンのどれかの1つの信号がHPS(ARMプロセッサ)に到達していないということになります。

f:id:fixme:20150124154134p:plain

ピン割付を確認してみます。信号線fpga_button_pioは3ビットのバスになっています。また、念のため、HelioボードのReference Manualの「4.7.1 User-Defined push button」を確認してみても、正しく接続されていることがわかります。ということは、ピン割付には問題はなく、すべてのプッシュボタンの信号はFPGAに接続されているということになります。

f:id:fixme:20150124153218p:plain

先ほどピン割付で表示されていた信号線fpga_button_pioからHPS(ARMプロセッサ)までの結線を確認していきます。Quartus IIのTools -> Netlist Viewers -> RTL Viewerを選択します。

f:id:fixme:20150124151540p:plain

結線を良く見ると、fpga_button_pio[2..0]がdebounce_instのdata_in[1..0]に接続されています。fpga_button_pioが3ビットなのに対し、data_inは2ビットのバスです。つまり、ここでプッシュボタンの信号がロストしていたわけです。

ちなみに、このdebounce_instモジュールは、スイッチをON/OFF時に発生するチャタリング対策のためのものです。(チャタリング対策は、ソフトウェア側でもできますが、リファレンスデザインではFPGA側でやっているということになります。)

また、以降に結線されている、data_out[1..0]とbutton_pio_external_connection[1..0]のいずれも2ビットのバスです。これらも3つのプッシュボタンの信号を伝達するには3ビットのバスが必要です。

button_pio_external_connectionはbutton_pioのパラレルI/Oポートですので、以上でプッシュボタンからbutton_pioまでの結線状況の確認は完了です。

まとめると、button_pioとdebouceフィルタの入出力のバス幅が2ビットになっているため、3つのプッシュボタンを動作させるには、これらのバス幅を3ビットに拡張が必要ということになります。

解決

button_pioのパラレルI/OのWidthを2ビットから3ビットに拡張します。

HDLを生成します。(Qsysのメニュー Generate -> Generate HDL)

button_pio_external_coneection_exportのバス幅が3ビットになったことを確認します。

helio_ghrd_top.vを開き、関連する箇所のバス幅を変更します。変更点は2点です。

1つ目は、debounceフィルター後の信号線fpga_debounced_buttonsです。バス幅を3ビットに拡張します。

  // wire [1:0] fpga_debounced_buttons; // 変更前
  wire [2:0] fpga_debounced_buttons;    // 変更後

2つ目は、debounceフィルターです。debounceモジュール内部のパラメータWIDTHを3ビットに拡張します。

// Debounce logic to clean out glitches within 1ms
debounce debounce_inst (
  .clk                                  (fpga_clk_50),
  .reset_n                              (hps_fpga_reset_n),  
  .data_in                              (fpga_button_pio),
  .data_out                             (fpga_debounced_buttons)
);
  // defparam debounce_inst.WIDTH = 2; // 変更前
  defparam debounce_inst.WIDTH = 3;    // 変更後

コンパイルします。

コンパイルが終わったら、RTL Viewerを開きます。バス幅が3ビットに拡張されているのを確認できます。

f:id:fixme:20150124151609p:plain

もうほぼ終わりです。後は、FPGAに書き込み、Linuxを起動後、以下のコマンドで3つ目のプッシュボタン(SW-13)に対応するGPIOを指定すると、無事3つ目のプッシュボタンも反応するようになります。

root@socfpga:~# modprobe gpio_interrupt gpio_number=151

サンプルのソースコードとモジュールのデプロイ先

問題は解決しました。しかし、gpio_interruptのソースコードのありかとmodprobeした時にどこからロードされているのか気になったので調べました。

まず、ソースコードですが、前回Linuxカーネルを自前でビルドするのに使ったビルドマシン(Ubuntu)にありました。具体的には、~/yocto/meta-altera/recipes-gsrd/pio-interrupt-altera/ です。Makefileを見ると、yoctoでgpio_interruptをビルドする時に、カーネルモジュールとしてデプロイされるように記述されています。

次に、gpio_interruptモジュールのデプロイ先ですが、gpio_interruptをmodprobeした時のシステムコールをstraceで解析してみると、gpio_interruptの実体が見つかりました。modprobe gpio_interruptすると、裏ではこのgpio_interrupt.koがロードされている、ということになります。

root@socfpga:~# ls -l /lib/modules/3.9.0/extra/
-rw-r--r--    1 root     root          4472 Jan 17 16:04 gpio_interrupt.ko

おわりに

原因を特定して解決できた時は、ハードウェアでもソフトウェアと同じで、とても楽しいです。 今回はじめてGPIOに入門したので、今後、もう少し踏み込んでみたいです。

HelioボードからSound Blasterで音が鳴るようにする

はじめに

今回は、Sound Blaster Play!をHelioボードに接続して音が鳴るようにしてみます。 大まかな作業としては、サウンドドライバを有効にしたカーネルをビルドして、SDカードにあるカーネルとDevice Tree Blobファイルの更新です。 中々うまくいかず試行錯誤しました。 今回の内容はFPGAとは関係無いですが、将来的に、FPGAで生成した信号を再生するのに活用しようと考えています。

f:id:fixme:20150118153414j:plain

準備

以下を用意します。

Sound Blasterは最新のものではなく、Linuxでの動作実績がある古めのものを使うことにしました。IntelEdisonボードで接続できた方がいるので、たぶん大丈夫だろうと考えました。

f:id:fixme:20150118153345j:plain

HelioのUSBポートがMini Bタイプなので、そのままではSound Blaster Play!が接続できません。 そこで、USB変換ケーブルを使って接続できるようにします。(間違ってMicro Bタイプの変換ケーブルを買ってしまい失敗しました。注意してください。また、最近はMicroタイプが主流のようで、家電量販店では見つかりませんでしたのでAmazonで購入しました。)

f:id:fixme:20150118153513j:plain

SDカードの読み書きがUbuntu仮想マシンからだと厳しそうなので、ネイティブマシンを用意します。 ビルド環境としてサポートされている、Ubuntu 12.04をセットアップします。

SDカードにはHelioでLinuxが起動するようにしたイメージを書き込んでおきます。以前、Linuxをブートできるようにした時のものを使用します。

カーネルのビルド

Helioの標準カーネルでは、Sound Blasterを動作させるデバイスドライバカーネルに組み込まれていません。 ですので、カーネルのビルドが必要です。 また、これからC言語Sound Blasterを操作したいため、カーネルにOpen Sound System APIを組み込みます。

GSRD v13.1 - Using Yocto Source Packageに書かれている手順に従ってビルド環境を作ります。

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install sed wget cvs subversion git-core coreutils unzip texi2html  texinfo libsdl1.2-dev docbook-utils gawk python-pysqlite2 diffstat help2man  make gcc build-essential g++ desktop-file-utils chrpath libgl1-mesa-dev  libglu1-mesa-dev mercurial autoconf automake groff libtool xterm
$ wget http://releases.rocketboards.org/release/2013.11/gsrd/src/linux-socfpga-gsrd-13.1-src.bsx
$ chmod +x linux-socfpga-gsrd-13.1-src.bsx
$ sudo ./linux-socfpga-gsrd-13.1-src.bsx
$ source ~/yocto/altera-init ~/yocto/build

カーネルのオプション設定を変更するため、以下を実行します。すると、新しい端末が起動し、変更メニューの画面が表示されます。

$ bitbake -c menuconfig -f virtual/kernel

以下を選択して、カーネルに組み込みます。

Device Drivers
 <*> Sound card suppor
  <*> Advanced Linux Sound Architecture
   <*> OSS Mixer API
   <*> OSS PCM (digital audio) API
   [*]  OSS PCM (digital audio) API - Include plugin system

設定を保存したら、カーネルをビルドします。

$ bitbake virtual/kernel

ビルドが完了すると、~/yocto/build/tmp/deploy/images以下にカーネル(zImage)が生成されます。 他にもファイルが色々ありますが、今回使うのはzImageだけです。

$ ls -l ~/yocto/build/tmp/deploy/images
[...]
-rw-r--r-- 1 fixme fixme   3351368  1月 18 11:04 zImage-1.0-r1-socfpga_cyclone5-20150118015746.bin

ビルドしたカーネルがサウンドドライバを組み込んでいるかざっくりした方法ですが確認してみます。zImageの方ではなく、圧縮していないカーネル(vmlinux)に対してstringsをかけてみます。カーネルのオプションを変更しないでビルドしたものと比較しても、明らかな違いが見られます。(vmlinux-1.0-r1-socfpga_cyclone5-20150118011738はカーネルのオプションを変更せずにビルドしたものです)

$ strings vmlinux-1.0-r1-socfpga_cyclone5-20150118015746 | grep snd | head -10
snd.cards_limit
snd.major
snd_disconnect_release
snd.slots
snd_timer.timer_tstamp_monotonic
snd_timer.timer_limit
snd_pcm_update_hw_ptr0
snd_pcm.maximum_substreams
snd_pcm.preallocate_dma
snd_rawmidi.amidi_map
$ strings vmlinux-1.0-r1-socfpga_cyclone5-20150118011738 | grep snd
msgsnd
msgsnd  
msgsnd  Td
3TCP: snd_cwnd is nul, please report this bug.

SDカードへのzImageの上書き

SDカードをマウントします。使用したマシンでは、以下のようにパーティションを認識してました。GSRD v13.1 - SD Cardの「SD Card Layout」の説明にある3つのパーティションが、mmcblk0p1~mmcblk0p3に対応します。

$ cat /proc/partitions 
major minor  #blocks  name

   8        0  312571224 sda
   8        1  308653056 sda1
   8        2          1 sda2
   8        5    3915776 sda5
  11        0    1048575 sr0
 179        0    7761920 mmcblk0
 179        1     512000 mmcblk0p1
 179        2    1536000 mmcblk0p2
 179        3      10240 mmcblk0p3

また、以下のようにマウントされています。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       290G   11G  265G   4% /
udev            1.9G  4.0K  1.9G   1% /dev
tmpfs           376M  904K  375M   1% /run
none            5.0M     0  5.0M   0% /run/lock
none            1.9G  7.8M  1.9G   1% /run/shm
/dev/mmcblk0p1  500M  3.5M  497M   1% /media/4628-37EE
/dev/mmcblk0p2  1.5G  162M  1.3G  12% /media/6e7ece6c-7d4f-43c3-89bc-4f9e228befce

ビルドしたzImageをSDカードに上書きします。試した環境では、zImageはSDカードの1つ目のパーティション(/dev/mmcblk0p1)にあるので、/dev/mmcblk0p1のマウント先である/media/4628-37EE/にzImageが存在します。GSRD v13.1 - SD Cardの「Updating Individual Elements on the SD card」が参考になりました。

$ cp ~/yocto/build/tmp/deploy/images/zImage-1.0-r1-socfpga_cyclone5-20150118015746.bin /media/4628-37EE/zImage

SDカードへのDevice Tree Blobの上書き

zImageの上書きだけでは、実はカーネルが起動しません。(ここで何度も試行錯誤してようやく答えにたどり着きました) Helio Prebuild Imagesで提供されているhelio.dtbで上書きする必要があります。SDカードに書き込まれている既存のdtbファイルのファイル名はsocfpga.dtbなので、helio.dtbをそのまま書き込むとうまくいかないことに注意します。

$ ls -l /media/4628-37EE/
合計 3160
-rw-r--r-- 1 fixme fixme   20584  9月  4 16:27 socfpga.dtb
-rw-r--r-- 1 fixme fixme     125  9月  4 16:27 u-boot.scr
-rw-r--r-- 1 fixme fixme 3202824  1月 18 10:27 zImage

以下の通り、dtbファイルを上書きします。

$ wget http://www.rocketboards.org/pub/Projects/HelioPrebuildImages/helio.dtb
$ cp helio.dtb /media/4628-37EE/socfpga.dtb

アンマウントしてからSDカードを取り出します。

$ sudo umount /dev/mmcblk0p1
$ sudo umount /dev/mmcblk0p2

なぜDevice Tree Blobの上書きが必要なのか (2015/01/24 追記)

考え方としては、以下でおそらく間違っていないと思います。

これを試した時は、Linux Kernel 3.13のSDカードイメージを使用していました。 一方、yoctoで自前でビルドしたカーネルのバージョンは3.9。 そして、Helio Prebuild Imagesは3.9向け。 なので、自前でビルドしたカーネルを動作させるには、カーネルと同一バージョンで作成されたDevice Tree Blobが必要になります。

Linuxの起動

SDカードをHelioに挿入して、電源を入れます。そうすると、Linuxが起動します。確認すると、uname -aの結果がビルドした日時になっていて、確かに自前でビルドしたカーネルで起動できています。

root@socfpga:~# uname -a
Linux socfpga 3.9.0 #2 SMP Sun Jan 18 10:58:42 JST 2015 armv7l GNU/Linux

Sound Blaster Live!の接続

用意しておいたUSB変換ケーブルを使って、HelioのUSB 2.0 OTG Port (J6)にSound Blaster Live!を接続します。

f:id:fixme:20150118153414j:plain

Linuxを起動した状態で接続しても認識しないようなので再起動してみます。

Linuxの再起動後、以下のコマンドを実行すると、Sound Blasterが認識されていることを確認できました。

root@socfpga:~# lsusb
Bus 001 Device 002: ID 041e:30d3 Creative Technology, Ltd Sound Blaster Play!
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

動作確認

カーネルのオプションの設定で「OSS PCM (digital audio) API」を有効にしました。 なので、/dev/dsp経由で音が鳴るようになっています。

試しに、適当なデータを/dev/dspに書き込んでみます。すると、「ビ!」っというような音が鳴ります。他にも適当なバイナリファイルを/dev/dspに書き込むと激しいノイズ音が流れます。

root@socfpga:~# echo aaaaaaaaa > /dev/dsp
root@socfpga:~# cat some.binary.file > /dev/dsp

動作確認としては、これで終わりです。あとは、/dev/dspに対して適切にオーディオデータを書き込んで楽しむのみです。

おわりに

うまくいくか不安でしたが、無事Sound Blasterを認識させて音を鳴らすことができました。 今後は、FPGAで生成した信号を鳴らしてみたいです。 Helioで音を鳴らしたい方の参考になれば幸いです。

Helioボード: FPGAに定数を出力する自作モジュールを追加してユーザーランドから値を取得する実験

はじめに

前回、Helioのリファレンスデザインに実装されているSystem IDをユーザーランドから取得する実験をしました。今回は一歩進んで、FPGAに自作モジュールを追加してみます。 と言っても、System IDと同じで定数を返すだけのものです。 ユーザーランドから自作モジュールにアクセスできるようにすることに注力します。

今回のハイライト: 以下の中央にある「deadbeef_0」を自作します。

f:id:fixme:20150124114046p:plain

自作モジュールのコード

ここは今回は注力すべき所ではないため手抜きです。

System IDコアをsynthesisしたら生成されるコードがsynthesis/submodules/soc_system_sysid_qsys.vにあります。 このコードを参考に、定数を出力するモジュールを実装します。 出力する値は、ぱっと見で正しいことがわかるように、16進数でdeadbeefとします。

System IDコアのインタフェースを踏襲するので、Avalon Memory-Mapped Interfaceのスレーブ側に接続可能なモジュールになります。

deadbeef.vとして保存します。

module deadbeef(
  clk,
  reset_n,
  address,
  readdata
);

  input            clk;
  input            reset_n;
  input            address;
  output  [ 31: 0] readdata;

  wire    [ 31: 0] readdata;
  assign readdata = 32'hDEADBEEF;

endmodule

自作モジュールの追加

ここが今回のメインの作業です。 Helioのリファレンスデザインをベースに自作モジュールを追加していきます。

1. Qsysを開きます。リファレンスデザインのsoc_system.qsysを開きます。

2. IP CatalogからNew Componentをダブルクリックします。

3. Content Typeタブにて、NameとDisplay nameに「deadbeef」と入力します。

f:id:fixme:20150116223548p:plain

4. Filesタブにて、Synthesis Fileにある「+」を選択します。「deadbeef.v」を選択します。 Analyze Synthesis Filesを選択します。

f:id:fixme:20150116223611p:plain

5. Signalsタブにて、図の通りであることを確認します。

f:id:fixme:20150116223649p:plain

6. Interfacesタブにて、avalon_slave_0のAssociated Resetにresetを選択します。 選択すると、先ほどまでMessagesに出ていたエラーが無くなります。

f:id:fixme:20150116223718p:plain

7. Finishを選択します。

8. Yes, Saveを選択します。

9. IP Catalogに追加されたdeadbeefをダブルクリックします。

10. Finishを選択します。

11. System Contentsタブにて、deadbeef_0のclock、reset、avalon_slave_0を結線します。

12. deadbeef_0のBaseアドレスを0x0001_0010に変更します。既存のsysid_qsysモジュールのBaseアドレスと重複しないように配慮します。

f:id:fixme:20150116223815p:plain

Schematicを確認してみます。単に雰囲気を味わうだけ。QsysのメニューのView -> Schematicを選択します。そうすると、以下のように中央にdeadbeef_0モジュールが配置されているのがわかります。Schematicを確認することで、より直感的に理解できます。

f:id:fixme:20150124114046p:plain

13. Generate -> Generate HDLを選択します。

14 デフォルトのままでGenerateを選択します。

15. 生成が終わったらQsysを閉じます。

コンパイルFPGAへの書き込み

Quartus IIでコンパイルしてsofファイルをFPGAに書き込みます。手順は以前のLチカと同じです。

ユーザーランドのコード

ここも今回注力すべきではないので手を抜きます。 以前実験で実装したコードをそのまま流用します。 以前のコードで「off_t SYSTEM_ID_BASE = 0x10000;」としていたアドレスを0x10010に変更します。これはdeadbeef_0のBaseアドレスです。変更するのは、ここだけです。

このコードを前回と同様の手順でクロスコンパイラでコンパイルして、Helioボードに実行ファイルをscpでコピーします。

実行

実行してみると、deadbeefモジュールで実装した「32'hDEADBEEF;」が出力されます。ユーザーランドからFPGAの自作モジュールにアクセスできました。

root@socfpga:~# ./sysid
System ID: 0xdeadbeef

参考情報

前回と同様のサイトのチュートリアルが参考になりました。

おわりに

自作モジュールにユーザーランドからアクセスできるようになりました。 HelioでFPGAをはじめる前よりかは、ハードウェアとソフトウェアのつながりがより理解できるようになってきました。 引き続き実験をしていこうと思います。

Helioボードのリファレンスデザインで実装されているSystem IDを取得する実験

はじめに

Helioボードのリファレンスデザインには、System IDと呼ばれるIPコアがFPGAに実装されています。 これは、バスを通して32ビットのIDを返すという単純なIPコアです。

f:id:fixme:20150113223230p:plain

System IDはARMプロセッサからアクセスできるようにインターコネクトされています。 LinuxのユーザーランドからこのSystem IDを取得する方法がわかれば、Linux、ARMプロセッサー、FPGAのつながりをより深く理解できると考えました。 そこで今回は、簡単なCのプログラムを通して、FPGAに実装されているSystem IDの値を取得する実験をしてみます。

(手抜きな)クロス開発環境の構築

こちらを参考にします。 これは、本来、ボードでLinuxを動かすために必要なU-Boot、カーネル、ルートファイルシステムを構築するための手順です。 でも、カーネルをビルドするという点に着目すると、どこかにクロス開発環境が作られるはずです。 と、予想して試してみると、/opt/altera-linux/linaro/以下にクロス開発に必要なツールチェーンがインストールされました。 今回は高度なことはしないので、これをクロス開発に使うことにします。

Ubuntu 12.04を用意して以下を実行します。(VirtualBox上にVMとして構築しました)

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install sed wget cvs subversion git-core coreutils unzip texi2html  texinfo libsdl1.2-dev docbook-utils gawk python-pysqlite2 diffstat help2man  make gcc build-essential g++ desktop-file-utils chrpath libgl1-mesa-dev  libglu1-mesa-dev mercurial autoconf automake groff libtool xterm
$ wget http://releases.rocketboards.org/release/2013.11/gsrd/src/linux-socfpga-gsrd-13.1-src.bsx
$ chmod +x linux-socfpga-gsrd-13.1-src.bsx
$ sudo ./linux-socfpga-gsrd-13.1-src.bsx

普通にgccが動きます。

$ /opt/altera-linux/linaro/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-4.7-2012.11-20121123 - Linaro GCC 2012.11) 4.7.3 20121106 (prerelease)
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

System IDをどうやって取得するのか?

Qsysでインターコネクトを確認します。

f:id:fixme:20150113222951p:plain

h2f_lw_axi_masterからsysid_qsysにインターコネクトされています。この「h2f_lw」は「Lightweight HPS-to-FPGA interface」を表しているようで、h2f_lw_axi_masterなので、「Lightweight HPS-to-FPGA interface」のマスター側です。それに対してsysid_qsysは、Avalon Memory Mapped Slaveという説明からすると「Lightweight FPGA slave」にあたるようです。

Cyclone V Hard Processor System Technical Reference Manualの「Table 1-2: Common Address Space Regions」よると、Lightweight FPGA slavesのベースアドレスは0xFF200000となっています。 このベースアドレスは、プロセス内のアドレスではなく、物理アドレスです。

f:id:fixme:20150113222925p:plain

sysid_qsysのベースアドレスは0x00010000です。Lightweight FPGA slavesからの相対アドレスです。

結局、System IDは、0xFF200000 + 0x00010000の位置を先頭に4バイト分ということになります。

コード

以下のコードを作成しました。/dev/memで物理アドレスにアクセスします。mmap(2)でLightweight FPGA slavesのベースアドレスである0xFF200000をプロセス空間のポインタにマッピングします。そのポインタに対して、sysid_qsysのベースアドレスである0x00010000を足した位置から4バイト分をSystem IDとして読み取ります。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    int fd;
    void* mapped_base;
    void* sysid;
    off_t LW_FPGA_SLAVES_BASE = 0xff200000;
    off_t SYSTEM_ID_BASE = 0x10000;
    int map_length = SYSTEM_ID_BASE + sizeof(int); // sizeof(int) = System ID length (4 byte)

    fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd < 0)
    {
        exit(1);
    }

    mapped_base = mmap(NULL, map_length, PROT_READ, MAP_SHARED, fd, LW_FPGA_SLAVES_BASE);
    if (mapped_base == MAP_FAILED)
    {
        close(fd);
    }

    sysid = (unsigned char*)(mapped_base + SYSTEM_ID_BASE);
    printf("System ID: 0x%x\n", *(int*)(sysid));

    munmap(mapped_base, map_length);
    close(fd);

    return 0;
}

ビルド&実行

予めGolden Hardware Reference DesignFPGAに書き込んでおきます。

ビルドして、Helioボードに実行ファイルを転送します。

$ /opt/altera-linux/linaro/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-gcc sysid.c -o sysid
$ scp sysid root@helio:

Helioボードで実行します。取得したSystem IDはQsysのSystem IDと一致しているので、正しく取得できています。

root@socfpga:~# ./sysid
System ID: 0xacd51402

f:id:fixme:20150113223230p:plain

参考情報

今回の実験では、以下を参考にしました。

おわりに

簡単な実験でしたが、ユーザーランドからFPGA側のデバイスにアクセスするコツがわかってきました。