2015年6月25日木曜日

ZYNQで追加のUARTポートを使う まずPL側

組み込み機器を作っていると、買い物コンポーネントをつないだりするために無駄にシリアルを多数用意しなきゃいけないことがよくあります。

最近のマイコンはそれを考慮して最初から4系統のUARTを持ってたりするのも多いですけど、5とか6系統あたりからだんだん選択肢が怪しくなります。

ZYNQの速度からすると非常にもったいないんですが、FPGAで欲しいだけの232Cポートを用意するのはけっこうよくある使い方です。

まず、ZYNQは最初からプロセッサ側に2ポートのUARTを持ってるので、vivadoでMIOのピン割り当てを有効にしてLinuxのdtsファイルにちょっと書くだけで(参考)1ポート増設はすぐできます。最初からデバッグコンソールに使ってるのと並んで、これは(digilent配布の)Linuxでは /dev/ttyPS0と /dev/ttyPS1として見えます。


3ポート以上要るときはPL部にIPをぺたぺた配置していかないといけません。

xilinx提供のIPには

  • 16550互換のゴツイもの。ドライバはLinux純正のでOK
  • ハードフロー関係を省いた軽量なuartlite。IPの実績は長いけどLinuxドライバが迷走中

の2つがありそうです。使い方はだいたい同じですがそれぞれ罠がありました。



まず本命のuartliteについて、PL側から説明していきます。
uartliteは軽くて小さいけど16バイトFIFOと割り込み対応はしていて性能はなかなのものです。

IP側は、まずZYNQのPL部設定で汎用ファブリック割り込みを有効にしておきます。まあこれはどの割り込みテストでも一緒。


後はxilinxの参考ページほぼそのままで、下図の右下部分がuartliteです。

デバッグ用に割り込みラインをLEDにも出してるのと、無駄に16550も配置してRXを両方に入れてたりしますが16550の方は無視してください。

参考ページと違うところは割り込みの番号です。

ZYNQに割り込みを突っ込むのにはORで好きな本数束ねた後に、上の絵の中央下端にあるconcatさんを経由してからPS部のIRQ_F2Pに突っ込むのですが、このconcatさんの仕様がある時点から変わっています。

昔は割り込み番号を大きいほう(91)から使っていたのが、最近のバージョンでは小さいほう(61番)から使うようになっています。(↓concatのダイアログに文章で書いてあります)

なのでOS側のためにdtsファイルに書く割り込み番号も、61側から順に書かないといけません。

なお、ハードフローを使わないならuart16550を使う手順もPL側は全く同じです。違いはuartliteでTX/RXと名づけられていた信号はsout/sinという名前になってる、てとこぐらい。あとfreeze機能は不要なので一応GNDにつないでおきました。

後はAXIバスのアドレスを自動でいいので割り振ってあげてください。
PL側の準備はここまで。

2015年6月9日火曜日

mmapでドライバ的なコードを書くのもアリなのでは?

UIOをあきらめたので素直に通常のドライバを勉強しよう、と思いつつも、単なるIOポーリングくらいだとあちこちで紹介されているmmapを使った手抜きコードでもいいような気がしてきます。

http://www.sweetcafe.jp/?p=171

http://www.wiki.xilinx.com/Linux+User+Mode+Pseudo+Driver

windowsの感覚から考えるととんでもない機能ですが、しかしそもそもユーザー空間とカーネル空間というのはMMU (とCPUの特権レベル)で作り出している概念なので、MMUを使って正式に両者を繋げるようなアクセスはいんちき技というわけでもないようです。


もちろん、ドライバの枠組みを通らずにユーザーコードからメモリマップドIOを操作する(=ハードウェアにアクセスする)のは、ソフトの移植性やハードウェアの隠蔽レベルを下げてしまう可能性はあります。

が、そこはソフトを書く人次第ですから、mmapを使うコードをきちんとカプセル化して"ドライバ"であると意識してプログラミングすれば保守性は問題ありません。

というか、ユーザーコードとハードウェア制御が混ざってカプセル化が崩れるという心配は全く同じことがUIOを使った場合にも言えるので、UIOがアリなんだったらもはやmmapで作ったドライバを邪道扱いする理由は無い気がしてきました。




・・・と、言い訳を並べたところで性能計測

DigilentのチュートリアルでPL側に作ったLEDレジスタ(0x43C30000)をmmapで100万回リード、ライトして時間を計測しました。

  • mmap経由で8ビットライト x100万回 = 0.23sec  ⇒ 1アクセス 230nsec
  • mmap経由で8ビットリード x100万回 = 0.21sec  ⇒ 1アクセス 210nsec

と、なかなかの速度です。1バイトアクセスならPCIexよりはるかに早い!


このままPIOでバカみたいにデータ転送しても数MB/sec出るわけなので通信系ドライバ程度ならこれで十分な場合もありそう。


書き込み100万回のコードを貼り付けておきます(ひでみさんのコード丸パクリです)
#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <fcntl.h>
#define SIZE 0x1000
#define BASE_ADRS 0x43C30000
#define REG_DATA 0x00
#define REG_TRI  0x01

void main(){
  int fd;
  unsigned int *buf;

  fd = open("/dev/mem", O_RDWR | O_SYNC);
  if (fd == -1) {
    printf("open failed");
    return;
  }

  buf = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, BASE_ADRS);
  if (buf == MAP_FAILED) {
    printf("mmap failed");
    return;
  }

  int cnt=0;
  int dat;
  while(cnt++ < 1000000){
    buf[REG_DATA] = 0x00;
    //sleep(1);
    buf[REG_DATA] = 0xFF;
    //sleep(1);
  }

  munmap(buf, SIZE);
  close(fd);
}
念のためopenのときにO_SYNCフラグを付けておきましたがこれの有無で特に動作、実行時間に変化は見られませんでした。

これを普通にコンパイルして、実行時に
time ./test2
で実行すると時間が計れます。

2015年6月8日月曜日

Digilentの最新リポジトリでもUIOの状況は・・・同じ

いっこ前の記事でxilinxのリポジトリ内容が古い・・・とフォーラムに書かれてたと言いましたが、フォーラムの書き込みは2013年のものです。

そしてZYBOユーザーの人はチュートリアルどおりに作業すると、dtsファイルのサンプルが入ってるのがmaster-nextなのであえて古いものを使っていることになります。

dtsファイル以外にはDigilent最新リポジトリでZYBOが困ることは特になさそうなので、最新をとってきて、dtsファイルだけmaster-nextから抜き出したものを使うのを試しました。


どきどきしながらdtsファイルにUIOのデバイス設定を書いてリブート・・・


やっぱり/dev以下にはuioデバイスは出現しませんでした。ちぇっ。

zynqのドライバ入門 UIOを使ってみたかった・・・が

先輩方のUIO解説記事 (例えば、https://formalism.github.io/blog/posts/2014/05/zynqpllinux-dts/) にしたがって、カーネルをmake menuconfigで確認すると、ZYBOチュートリアルで拾ってきたものでもちゃんとUIOには[*]チェックが入っています。

楽勝~と思って、dtsにそれっぽいデバイスをいろいろ書いて、dtbをSDカードにつっこんでリブートすると、/devの下にuio0とかが・・・無い!

いくらやっても、カーネルをリコンパイルしてもどーやっても出現しません。



しばらく苦戦しているうちに、ふと先輩方の多くが、Digilentのチュートリアルをさらっと流した後にdigilentリポジトリではなく、http://masahir0y.blogspot.jp/2014/01/u-boot-linux-kernel-zynq.html のように公式リポジトリや、その他のディストロを使っていることに気づきました。

そういう目でぐぐっていると
http://forums.xilinx.com/t5/Embedded-Linux/help-with-simple-Zynq-PL-device-access-via-Linux-UIO-based-user/td-p/270836
に、

「xilinxのカーネルは古いので、UIO機能自体は入っているけどdtsを解析してuioデバイスを作る機能が入っていない。だからUIOを使うには自分でデバイスを作るカーネルモードのコードを書かなきゃいけない」

と書かれていました。

うーん、困りました。
そもそもUIOの紹介ドキュメントに「UIOは何一つ新たなものを実現しません。ただ単にわかりやすく、特権コードを書かなくていいだけです」と言ってるくらいなので、どうせカーネルモードのコードを書のなら無理してUIOを使う必要が無さそう。

かと言って、UIOのためだけに使用するカーネルのソースを別のにするというのも何か違っている気がします。(もちろん公式が新しいわけだし、変更点を理解している人なら全然問題ないんでしょうけど)

というわけで、digilentまたはxilinxリポジトリのカーネルを使う限りはUIOではなく普通のドライバ記法を勉強することになりそうです。

zynqのドライバ入門 myledサンプルから一歩進みたい

Digilentのmyledサンプルまではフォローできました。これでFPGA(PL)にAXIで繋いだレジスタへのwriteができ、同じものをreadしてCPU側で表示することができました。

この先やりたいことは組み込み用途ならだいたいみんな一緒で

  1. myledサンプルだと、一度read/writeするたびにcloseしないといけないので遅い。連続rwしたい。
  2. 割り込みハンドリングしたい
  3. 大容量データを転送したい

といった順序で戦っていくことになります。



まず1番目についてですが、現状の/proc/myledデバイスを open->read->close または open->write->close するコンボでどれだけオーバーヘッドがあるか、ユーザーコード側で100万回ループさせて確認してみました。

readの方はコンソールに文字を出すと/dev/nullに捨ててもかなりのオーバヘッドになるので、コード内で読み捨てしています。

  • open->write->close ×100万回 = 156秒  ⇒ 1回あたり 0.15msec
  • open->read->close ×100万回 = 76秒    ⇒ 1回あたり 0.07msec

人間の速度からすれば高速ですが、10msecごとにIOをポーリング監視したい、といったような組み込み用途ではさすがに見過ごせない遅さです。


3番目の大容量転送は、DMAやらブロックデバイスやらが登場しそうな気がするのでいったん忘れることにして、とにかく1番と2番をなんとか学習したいと思います。


あちこちの先輩方の記事を見ると、このあたりに関してはUIOを使うと簡単そうなのでちょっと試してみたのですが・・・

2015年6月5日金曜日

ZYBOでLinuxブート時にUSB phyがmissingで初期化されない問題

とりあえずDigilentのチュートリアルベースでいろいろいじれるようになりました。

いろいろ試すのに母艦とのファイル受け渡しをやっていて、LANが使えない環境だとUSBメモリを使って受け渡ししたくなるのですが、Linuxブート後のZYBOにUSBメモリを挿しても認識されません。

おりょ?と思ってdmesgで確認すると
ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
ehci-pci: EHCI PCI platform driver
zynq-dr e0002000.ps7-usb: Unable to init USB phy, missing?
確かに初期化時に失敗しています。


DTSが不適切だったかなぁ?と思って確認しましたが、特にDigilentのZYBO用から何も変更していないのでおかしくなりようがありません。


同じように困ってる人がいないかな~と思ってぐぐっていると

http://www.sweetcafe.jp/?p=322

起動時に何かデバイスを挿していないとこのようにphyが見つからず失敗するとのこと。


OTGのホスト判別機能が関係しているのは間違いないですが、ソフト的な対処法がわからないので、ハードウェア的にホストであると教えてやることで当面やりすごすことにします。



OTGに対してホストであると教えるには、OTGのID線をGNDに落としておけばいいので

  • 何かデバイスを挿しておく
  • 市販のOTG⇔USBA変換コネクタをマイクロUSBポートの方に挿しっぱなしにしておく

のどちらかで可能です。たまたま変換コネクタが手元にあったのでこれを使うことにします。

2015年6月3日水曜日

(メモ) zynqのUSBをデバイスポートとして使う例

zynqのUSBをホストとして使う分にはlinuxの情報が山ほどあるしそもそもプログラミングすら不要な場合が多いですが、デバイスとして使うのはとっかかりが無いと難しそう。

もちろんサンプルは用意されてるのですがhands on tutorialほどには低レベルな読者を想定していないので苦労しそう・・・

と思っていたら、ありがたいことに日本語で例を挙げてくださってる方がいました

https://formalism.github.io/blog/posts/2014/04/zynqusb/

https://formalism.github.io/blog/posts/2014/04/zynq-usb/

procファイルシステムってなに?

ZYBOのチュートリアルを一通り終えた。
/proc/myledデバイスを登録してリードライトが可能になったので、原理的にはハードウェアとの情報のやり取りはなんでも可能ぽぃ。

けど、このサンプルの通りだとアクセスするたびにfcloseしてopenし直さないといけないのでオーバーヘッドがとてもでかいです。

試してみたら

  1. fopen
  2. fputcでLEDの状態を変更
  3. fclose

のセットをを100万回やって2分36秒でした。1回あたり0.23msかかっていることになります。

※後でもう少しちゃんと計りなおしました


ど論外というほど遅くはありませんが、大半がfopen/closeのオーバーヘッドだと思うのでオープンしっぱなしでアクセスしてキャラクタデバイスの限界速度を知っておきたくなります。


というところで、キャラクタデバイスの中でもこのサンプルはprocfsの枠組みを使っているそうなので、procfsについてまずお勉強からです。

http://www.turbolinux.com/solution_service/library/features/c_magazine/vol_03.html


勉強しているうちに、ZYBOのサンプルはprocfsの中でも、さらにseq_fileインターフェイスを使ってバッファリングしていることがわかりました。

http://wiki.bit-hive.com/tomizoo/pg/proc%20fs%20%A4%F2%BB%C8%A4%C3%A4%BF%A5%AB%A1%BC%A5%CD%A5%EB%BE%F0%CA%F3%A4%CE%C9%BD%BC%A8%20%A4%BD%A4%CE2


ZYBOのチュートリアルだとopenの時にseq_printfしてドライバとしてはreadのための処理を終えちゃっていて、後は1バイトずつ読出しがかかるたびにseq_readが受け渡しをやっているようです。

この方式だと、openして、大量のデータをどがーーーっと読み出してcloseするようなある意味ブロックデバイスのようなデータ転送がキャラクタデバイスでも簡単にできそうです。



まあ、それはそれで後で転送速度とか調べるとして、逆にIO監視のようなリアルタイムちびちび再読出しを(毎回のopen/closeオーバーヘッド無しに)するにはどうするか…。seq_fileを使わない方が簡単そうですね。


追記:
seq_fileを使わない方は、create_procの書式が最近変更されていて古い記事がそのまま使えなかったので新書式のほうのサンプルからスタート
http://pointer-overloading.blogspot.in/2013/09/linux-creating-entry-in-proc-file.html

2015年6月1日月曜日

zybo起動用ramdiskの編集

ひととおりチュートリアルをフォローして、SDからのlinuxのブートまでこぎつけました。

linuxカーネルはdigilentから拾ってきたものそのままですが、ビルド前にmake menuconfigするとそれなりのGUIが出るので無知でも多少のカスタマイズはできそうです。

となると、しろうとシステム的にはramdiskイメージさえ編集できれば自作ソフトを起動したりSDの別パーティションをマウントしたりとやりたいことの大半はできるわけです。


といっても、マウントして操作するだけです。

digilentから拾ってきたサンプルのramdiskが ramdisk8M.image.gz だとすると


  1. まずgzip -d ramdisk8M.image.gz で解凍
  2. 適当な名前でマウント  sudo mount ramdisk8M.image /media/ramTest/
  3. /media/ramTestに移動して好きなように編集しまくる
  4. cdで ramTest以下から抜けて、umount
  5. gzipで再圧縮
  6. 後はチュートリアル通りubootヘッダを付加してSDに入れ直し


よりストイックに、ゼロから作成したい場合は

http://www.ibm.com/developerworks/jp/linux/library/l-initrd/

なんかが参考になりそうです。

Linuxブートまでの参考ページ

ZYBOにLinux導入お勉強の、参考ドキュメントはぶっちぎりで digilentのhands-on tutorial が良いのですが、使っているバージョンが2014.1であることや、些細な誤記程度はあるので正誤表的に解説してるページを追加で参考にすると良いです。

自分で書こうと思ってメモしながらやっていたのですが、Begin a nerd engineerの内川さんの記事に全てが書かれていたので、こちらをリンクしておきます。

http://nerdengineer.com/jp/blog/files/09c7e46c28ab96e4ecce1800b9b11575-3.html

とても助かりました。