ModelSim Altera Starter Editionが起動できない問題のパッチ
はじめに
Quartus II 14.1 Web Editionをインストールした時に、ModelSim Altera Starter Editionも合わせてインストールしたのですが、ModelSimが起動できない現象が発生して使えない状態が続いてました。これを解決しないことにはシミュレーションができないので、時間を取って解決方法を探しました。期待する答えが中々見つからず、思いのほか苦労しました。
今回は、この問題を解決するパッチを見つけたので紹介します。
現象
ModelSim Altera Starter Editionを起動しようとすると、以下のダイアログが表示されて起動できません。
解決
こちらにパッチがあるので適用します。 これで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
おわりに
LinuxとFPGAでSDRAMをシェアできるようになりました。 ただし、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
全体像
シグナルジェネレータで生成された信号データをオンチップメモリーに書き込みます。ざっくりした流れは以下の通りです。
- CPUがDMAコントローラの転送を命令する。
- DMAコントローラが転送開始する。
- シグナルジェネレータで信号を生成しFIFOに送信する。DMAコントローラはFIFOから読んでオンチップメモリーに書き込む。
- 指定した信号長をオンチップメモリーに書き込んだら、DMAコントローラは処理を終了する。
- 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でFIFOとFIFOと接続すると以下のエラーが発生します。
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-MM write slave to Avalon-MM read slave
- Avalon-ST sink to Avalon-ST source
- Avalon-MM write slave to Avalon-ST source
- Avalon-ST sink to Avalon-MM read slave
今回の場合は、前述した通り、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コントローラへの大まかな指示手順は以下の通りです。
- コピー元アドレスを書き込む
- コピー先アドレスを書き込む
- 転送データ長を書き込む
- その他制御情報を書き込み、転送を開始する
- 転送完了したか確認する
最後の5は、普通は割り込みを使うと思いますが、まだそのやり方が理解できていないので、DMAコントローラに問い合わせます。
QsysのIP CatalogからDMA Controllerを探してウイザードを開始します。設定はデフォルトのままにします。
結線
Qsysで以下のように結線します。dma_0、fifo_0、signal_generator_0が今回追加したインスタンスです。
- 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ステップです。
- コピー元アドレスを書き込む
- コピー先アドレスを書き込む
- 転送データ長を書き込む
- その他制御情報を書き込み、転送を開始する
- 転送完了したか確認する
この通りに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コントローラのレジスタを直接操作して動作確認する、といったことが可能になります。
メモリー読み書きユーティリティ
こちらで紹介されている、コマンドから任意のアドレスに対して読み書きできるユーティリティーを導入します。
このユーティリティでやっていることは、以前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とあります。
オンチップメモリーのベースアドレスは、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になります。
実際にやってみると、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ビットに拡張します。
プッシュボタンの割り込み検出サンプル
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プロセッサ)に到達していないということになります。
ピン割付を確認してみます。信号線fpga_button_pioは3ビットのバスになっています。また、念のため、HelioボードのReference Manualの「4.7.1 User-Defined push button」を確認してみても、正しく接続されていることがわかります。ということは、ピン割付には問題はなく、すべてのプッシュボタンの信号はFPGAに接続されているということになります。
先ほどピン割付で表示されていた信号線fpga_button_pioからHPS(ARMプロセッサ)までの結線を確認していきます。Quartus IIのTools -> Netlist Viewers -> RTL Viewerを選択します。
結線を良く見ると、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ビットに拡張されているのを確認できます。
もうほぼ終わりです。後は、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で生成した信号を再生するのに活用しようと考えています。
準備
以下を用意します。
- Sound Blaster Play!
- USB変換ケーブル
- Linuxネイティブマシン (SDカード書き込み&カーネルなどのビルド用)
- Linuxが起動するSDカード
Sound Blasterは最新のものではなく、Linuxでの動作実績がある古めのものを使うことにしました。IntelのEdisonボードで接続できた方がいるので、たぶん大丈夫だろうと考えました。
HelioのUSBポートがMini Bタイプなので、そのままではSound Blaster Play!が接続できません。 そこで、USB変換ケーブルを使って接続できるようにします。(間違ってMicro Bタイプの変換ケーブルを買ってしまい失敗しました。注意してください。また、最近はMicroタイプが主流のようで、家電量販店では見つかりませんでしたのでAmazonで購入しました。)
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!を接続します。
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」を自作します。
自作モジュールのコード
ここは今回は注力すべき所ではないため手抜きです。
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」と入力します。
4. Filesタブにて、Synthesis Fileにある「+」を選択します。「deadbeef.v」を選択します。 Analyze Synthesis Filesを選択します。
5. Signalsタブにて、図の通りであることを確認します。
6. Interfacesタブにて、avalon_slave_0のAssociated Resetにresetを選択します。 選択すると、先ほどまでMessagesに出ていたエラーが無くなります。
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アドレスと重複しないように配慮します。
Schematicを確認してみます。単に雰囲気を味わうだけ。QsysのメニューのView -> Schematicを選択します。そうすると、以下のように中央にdeadbeef_0モジュールが配置されているのがわかります。Schematicを確認することで、より直感的に理解できます。
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コアです。
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でインターコネクトを確認します。
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となっています。 このベースアドレスは、プロセス内のアドレスではなく、物理アドレスです。
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 DesignをFPGAに書き込んでおきます。
ビルドして、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
参考情報
今回の実験では、以下を参考にしました。
- Exploring the Arrow SoCKit Part III - Controlling FPGA from Software
- Tutorial:Getting started with FPGA-SoC and Linux Yocto on Terasic DE1-SoC board
おわりに
簡単な実験でしたが、ユーザーランドからFPGA側のデバイスにアクセスするコツがわかってきました。