本記事はHardware Description Language Advent Calendar 2025向けの記事です。
FPGAこそのプログラミング
RTLなどのプログラミングを覚えると、それを活用してみたいと思いますよね。その際にFPGAを使うのはLSI開発に比べて手軽な選択肢の1つです。
そしてマイコンではなくFPGAでRTLを書くメリットの出し方としては大きく2つ
- マイコンなどでもできることをFPGAでもっと効率的にやる
- そもそもマイコンなどでは出来ないことをFPGAでやる
が、あると思います。
昨今では特に AIアクセラレーターのようなものをFPGAで挑戦したいという人も増えたことから前者に挑戦する人も増えていますが、明らかな差別化として後者のFPGAでしか出来ない領域に手を出すこともお勧めです。特にFPGAでしか出来ない機能と組み合わせることで前者の技術も生きてきます。
イメージセンサとの接続
「マイコンでは難しいがFPGAなら実現できる」ものの1つに、イメージセンサーとの接続があります。もちろん世の中にはMIPIなどのインターフェースを備えておりマイコンに接続可能なイメージセンサも一部に存在しますが、そうでないものが多数存在します。
イメージセンサーと言っても色々な種類のものがあります。大半は CCDイメージセンサとCMOSイメージセンサに分類できますが、それ以外の特殊なものもあります。
また、CMOSイメージセンサーの中にも、高速度カメラや赤外カメラや偏光カメラのようなものからイベントベースのイメージセンサーやSPADセンサーまで、本当にあらゆるものがあります(使ったことないものが多いですが)。
一般的には組み込みマイコンから使うイメージセンサはRGBのローリングシャッターが大半ですので、ちょっとした特殊撮影を行いたい場合にFPGAが使えると、イメージセンサの選択肢が一気に広がります。
CCD(Charge-Coupled Device)イメージセンサーは原理的にグローバルシャッター撮影となり、これはこれでとても面白いのですが、センサーとしてはほぼアナログデバイスで、アンプやADCや、その名の由来となっている内部のCCD構造の制御信号や、そのためのマイナス電圧含めた多系統の電源準備なども外部に必要で、アナログフロントエンドチップとFPGAなどを組み合わせて、さらにそれなりのアナログ回路を設計することになります。
一方で、CMOSイメージセンサは、その構造上様々な制御回路を内部に構成でき、電源や出力信号もシンプルであるため、外部から見ると、かなりの部分をデジタル回路設計の知識で対応することができます。
ちなみに様々なインターフェースタイプのCMOSイメージセンサがあることについて「どうしてMIPIとかに統一しないの?、なぜマイコンに繋がらないCMOSイメージセンサがあるの?」という疑問を持たれた方もおられるかもしれません。筆者はイメージセンサーのユーザーでしかないので設計する側の知識はありませんが、想像するに
- デバイス性能を最大限引きだそうとすると規格に合わない部分が出てくる
- 画質を上げるためにノイズ源/熱源となるデジタル回路は最小限にしたい
- 独自インターフェースの中での後方互換性の確保
などがあるのではないかと思います。
一方でFPGAなどを用いてこれらにきめ細かに対応が出来れば
- MIPIなどの標準仕様からはかけ離れたデバイス固有機能で特殊撮影が行える
- システム最適化された高リアルタイム性/低コスト/低消費電力な設計ができる
などのメリットが生まれます。これは
- RGBカメラだけで画像処理研究している人たちの一歩先を行ける
(アクティブセンシング/マルチスペクトル撮影/特殊撮影各種) - 低遅延で現実世界にフィードバックを掛けたときに起こる事象変化自体を研究対象にできる
などのメリットを生み出します。
イメージセンサの映像信号をFPGAで受信する方法
信号伝送の方式
イメージセンサに限らないのですが高速で大量のデジタルデータを送る方法は多数存在し、本当に様々なイメージセンサーがあります。
- クロック同期シングルエンド [LVCMOSなど]
- クロック同期差動信号 [MIPI/LVDS/subLVDSなど]
- CDR(Clock Data Recovery) [SLVS-EC など]
幸いなことに昨今の多くのFPGAがこれらに対応しています(どちらがどちらに合わせて進化したのかなどはいろいろありそうですが)。
CDR は基本的にはFPGAのハードマクロのトランシーバーコアを使う事になりますので、本記事では触れず、シングルエンドや差動信号によるクロック同期での信号受信について触れていこうと思います。
クロック同期高速データ信号の受信方法
これらはクロックとデータが別々の信号線で送られることを前提としており、しばしデータの方は複数のチャネルが存在します。
そしてこれらにはクロックとデータとの関係がいくつかあります
- クロックの片方のエッジを使うもの(SDR)
- クロックの立ち上がりエッジと立ち下りエッジの両方を使うもの(DDR)
- クロックを1:7などで逓倍して使うもの
またセンサー出力のデータが変化する位相関係においても
- センサー出力のクロックエッジと同時にデータも変化するもの
- クロックエッジがデータの中央付近に来るもの
などがあります。まずはイメージセンサーのデータシートをよく読むことが必要です。
また、低速な信号はクロックエッジなどを見ておけばいいのですが、昨今の高速なイメージセンサーのデータを受信するには、配線遅延やそのバラツキや個体差や温度変化や経年劣化での変動を意識する必要があります。
本記事では今回はそのあたりにフォーカスしていきたいと思います。
配線遅延への対応
以降はFPGAメーカーによってできることも変わってきますが、配線遅延への主な対処方法として
- クロックを受信するピンの遅延タップを調整する
- データを受信するピンの遅延タップを調整する
- 受信したクロックの位相をPLLで調整する
と言うものがあり、特にデータの遅延タップ調整はデータ線個別に調整することもでき、基板の配線を等長配線しただけでは取り切れない細かなバラツキまで対応できます。
また、これらの遅延調整も
- 製造後の基板に合わせてソフトウェア設計時に値を決定し固定する
- 運用時にイメージセンサ起動の度に自動調整を行う
- イメージセンサ起動後も適応的に自動調整しながら動く
などなどあり、イメージセンサーの方も、起動時やブランキング期間で調整の為のトレーニングパターンを出力できるものもあります。
これらをどこまで拘って実装するかは、すべてRTLで記述できますのでプログラマの腕次第で様々なレベルで実装可能です。
もっとも、FPGAベンダーが提供するIPやアプリケーションノートのサンプルがそのまま使えるケースも多いので、それらをそのまま使う事も多いです。
一方で、リソースの節約や電力削減であったり、移植性の確保であったり、様々な理由であえてそれらを使わずに自分でRTLを書くこともあります。
自動遅延調整の考え方
ここから先は少し具体的な説明も行う為に必要に応じて AMD(Xilinx)の 7シリーズの仕様も引用します。ただし他のFPGAでも基本的な考え方は応用できると思います。
トレーニングパターンがある場合
トレーニングパターンがある場合は比較的扱いが簡単です。また SERDES を使う場合のアライメント探しに bitslip などの調整もここで行う事が多いです。場合によっては極性反転の自動検出なども可能です。
イメージセンサーの多くは SPI や I2C などのインターフェースで制御できますので、レジスタ仕様を見ながらまずはトレーニングパターンが出力されている状態まで設定します。
私は基本これらの制御もRTLで書いたり、マイコンを使いがちですが、こんなのやこんなのを使って開発するのもよく見かけます。
とにもかくにも「今こういうパターンが受信されるはず」という状態になれば、あとはPLLの位相や遅延タップなどを変動させてそのパターンが受信できる範囲を調べ、範囲の中央に位相調整します。
例えば 7シリーズの MMCM の場合 UG472 を見ると、下記のような信号が見つかり、位相調整が行えます。この調整機構の素晴らしいところは運用中にも動かしたまま微調整が出来る事で、適応的な調整も可能です。

また遅延タップに関してはUG471に IDELAYE2の記載があり、各I/Oに配置されているIDELAYE2を信号が通るように適切に記述したのちに、同じように遅延調整ができます。

なお、IDLEAYE2 を利用するには別途 IDELAYCTRL に基準クロック(200MHz)を供給しておく必要があります。これは基本的には IDELAYCTRL インスタンスを生成してクロックを接続しておけばOKですが、利用するI/Oを限定して最適化したい場合は IODELAY_GROUP などの属性でコントロールできます。7シリーズのライブラリガイドを参照ください。
トレーニングパターン無しで調整する場合
決まったトレーニングパターンを出す機能が無い場合、受信した値が正しいのか化けているのかデータだけでは判定できないため、もう少し複雑な方法で調整を試みることになります。
このレベルの調整が必要となる高速信号では LVDS などの差動伝送が使われるケースが多いですが、IDLEAYE2 などの調整可能な遅延タップはシングルエンド利用も加味してI/Oの数だけ存在する為、差動ペアの受信時には1つの信号に IDLEAYE2 を2個使う事が出来き、XAPP1017 から引用すると下記のような構成が可能です。

1つの差動受信(IBUFDS)からの受信を2つの遅延量設定の異なる IDELAYE2 を通した後に同様に ISERDES でデシリアライズして比較することが可能になります。
この時、信号のEye(綺麗に0/1を判別できる範囲)が十分開いており、調整マージンが一定量ある状態を仮定すると Master 側の IDLEAY の調整範囲に十分なマージンが取れている状態では Slave 側の IDLEAY を多少 Master とずらしても同じ値が得られるはずですし、異なる値が得られる場合は少なくとも何らかの調整が必要な状態にあることがわかります。したがってオール0やオール1などの、差が取れないデータを除外したのちに Slave側 との値を比較しながら自動調整を行う事が可能です。
運用方法も様々で、ある程度必要なマージン幅やEyeの幅が既知であれば、Master と Slave の遅延差を一定幅になるように設定し、Slave側が一致するかしないかの境界に来るように、例えば一致すればPLL位相を遅らせ不一致なら進めるといった適応制御も可能です。
bitアライメントのやりかた
ここまででデータを適切なタイミングで取り込めるようになったとして、次にbitの区切りがどこにあるのかと言うアライメントの調整があります。
AMD の SERDES だと bitslip という信号があり、デシリアライズするときの区切りをずらしていくことができます。
トレーニングパターンを使う場合は、目的のパターンになるまで bitslip を繰り返すことになります。そうでない場合でも、SYNCコードなど絶対にそのbitパターンはそこでしか現れないようなコードが用意されている場合など何らかの方法でbitの区切り位置を満たしすまで、カウンタを用意して、タイムアウトするまでに目的のコードが取れなければ bitslip させます。
また贅沢にバレルシフタを置くようなことも可能で、あらかじめシフトさせたマッチパターンを複数用しておいて並列比較してSYNCコードを見つけてその順に後から並べ替えてしまうような方法も取れます。この方法だと捨てるデータが発生しないため毎ラインの先頭で再アライメントしないといけないようなケースにも対応できます。
もう一つ、逓倍クロックを用いる場合は、クロック信号自体がアライメント位置を表すケースもあります。7:1 アライメントなどが有名で XAPP585 などに詳細があります。
ドキュメントを引用させてもらうと下記のようになっており。

Received Clock の位相からどのビットがワードの開始かを知ることができます。このやり方の場合、クロック自身もPLLに入力するだけでなく、ISERDESに入力するパスを持ち、クロック波形パターンを見ることになります。

7:1方式はディスプレイパネルなどでよく使われるようですが、カメラでも CameraLink などの仕様で見かけることができます。
(余談) set_input_delay などの設定
外部同期の信号の受信にあたって sdc ファイルなどに set_input_delay などの制約を書くことをご存じの方もおられると思います。実際にこれらの設定はしばし重要です。
一方でここまでの話を見て気づいた人もおられるかもしれませんが、AMDの7シリーズの場合、IDELAY や SERDES や PLL/MMCM などや、クロックバッファ(BUFIO/BUFR)などは、ハードウェア的に決まった場所にしか存在せず、今回のような使い方だと RTL を書いた段階で利用されるリソースが一意に確定してしまう為、合成器や配置配線ツールにはなんら頑張る余地がないケースがあります。
そうなると set_input_delay の設定は結局のところワーニングを抑止するための気休めのようなものになってしまうケースもあるように思います。
イメージセンサーの設定の制御
イメージセンサーで設定できるものは多岐にわたりますが、FPGAからの制御で重要なものとして大きく
- シャッタータイミング(露光時間)
- 撮影パラメータ(ROI/ゲイン/黒レベルなど)
の2つです。
シャッタータイミングの制御
シャッタータイミングの調整と言うと普通の写真の撮影においては自動露光調整や、高速シャッターと長時間露光を組み合わせたHDR撮影などが思い浮かぶと思いますが、マシンビジョンも範疇に入れるともっと様々な使い方ができます。
多くのグローバルシャッターカメラは、FPGAからかなり厳密にシャッタータイミングを入力することが可能で、これは照明などの制御と連携することで様々な特殊撮影やアクティブセンシングを可能とします。
これは本当に様々なことが可能で、ストロボ照明のフレーム単位でON/OFFを繰り返して撮影して差分を取れば背景光を削除できますし、左右のストロボ照明を交互に点灯させれば照度差ステレオも可能です。さらには高速度カメラなどでマルチスペクトル照明での撮影も可能です。
他にも、カラーカメラを使い1つの露光区間で R-G-B のストロボ照明を順番に発光させた場合、1枚の画像の中で、R画素、G画素、B画素はそれぞれ少し時間のずれたタイミングを撮影していることになりますので、1枚のフレームに3つの時刻の撮影が重畳出来たりもします。
またこれらは、固定的に行うのではなく、前のフレームの画像処理結果に応じてさらに適応的に次のフレームのセンシング内容を動的に変化させることも可能です。
撮影パラメータの制御
撮影パラメータはSPIやI2Cで設定することが多いですが、決まった時刻までに設定しておくことで次フレームの撮影時に反映されます。
これもFPGAであれば極めて容易に時間保証できますので、2つ前のフレームの画像処理を使って、次のフレームの設定を動的に変更することが可能です。
オーソドックスには、自動ゲイン調整などですが、センサーによってはもっと大胆にROI(センサーの一部だけ読み出す機能)の位置を変えたり、フレームレートを変えたりすることもできます。
実際のイメージセンサーの事例
オンセミ社 PYTHON300の概要
ここでは私が開発しているオンセミ社のPYTHON300イメージセンサーとAMD社の Spartan-7 FPGA を組み合わせた、グローバルシャッターMIPI高速度カメラ を例に説明したいと思います。
早速データシートを眺めてみると、1ページ目のからさっそく
- PYTHON 300: 640 x 480 Active Pixels
- 4 LVDS Data Channels
- 815/545 frames per second @ VGA (ZROT/NROT)
- Each channel runs at 720 Mbps
などの、なかなかワクワクする単語が見受けられます。
VGA(640×480)という小さいサイズながら、815fps などの撮影が出来てしまうようです。
インターフェースとしては 4チャネルの LVDS があり、それぞれ DDR で 720Mbps のデータを出してくるようですね。トータルで 2.88Gbps の帯域があることになります。
起動と終了
イメージセンサーを利用するには起動と終了が重要です。PTHON1300シリーズのデータシートにもちゃんと記載があり下記のような記載がありました。

今回は各電源系とEN信号をFPGAに繋ぎ、クロックもFPGAから供給するようにして、この部分の制御もすべてFPGAで行いました。
正しい手順で起動/終了すればこれでも十分なのですが、「いきなり電源を抜く」というパソコンなんかでもやってはいけない事をやると、違反となり、破損の可能性が出てきます。
そこで念のため別途入力電源を見張ってPGOOD信号を生成して、電圧低下を感知した時点で Power Down シーケンスが強制的に走る仕組みも入れました。FPGAのコア電圧自体は1.0Vなどですので、I/O電圧が下がり始めてもしばらくの間はバイパスコンデンサに溜まっている電力で動作できます。システム全体としてある程度コンデンサ容量を持っていれば保険になります。
(もっとも、今回のカメラを接続する対象は ZYBO や KV260 など、SDカードで Linux を動かすシステムですので、そもそも突然の電源切断はそちらにもダメージを与えてしまいますので、正しくON/OFFすべきなのですが)。
SPIからレジスタを読み書きしよう
世の中のイメージセンサーの中には電源を入れると早速映像を出し始めるものもありますが、多くのイメージセンサーは適切な値を制御レジスタに書かないと起動しません。
PYTHON300の場合は SPI 端子からレジスタの読み書きができます。
データシートにもきちんと読み書きの波形が記載されています。

9bitのアドレスと、Write/Read判定の1bitと、16bit のデータを使う通信のようです。
このような通信回路を書くのはFPGAの得意とするところですので、RTLの勉強にも最適です。
SPIからレジスタに様々な値を書くことで、イメージセンサを起動させたり、トレーニングパターンを出したり、撮影範囲(ROI)や、露光時間、ゲイン、フレームレートなど様々な設定が行えるようになります。
なお、SPIでのレジスタ設定をRTLだけですべて書くことも可能ですが、今回のカメラはZYBOやKV260にMIPIケーブル経由で接続する構成にしていたので、LinuxからまずI2C経由でカメラにアクセスして、そこからさらに上記のSPI仕様でアクセスできるように作りました。ただ Spartan-7 と別FPGAの通信は今回の記事の範疇ではないので割愛いたします。
高速映像信号を受信しよう
センサーには8bitモードと10bitモードがあるようですが、今回は 10bit モードに設定して使う事にしました。この場合720MbpsでSERDES後のデータが10bitになります。8bitモードではその場合は 576Mbps になり結果的に同じピクセルレートのようです。またデータの 4ch の他に、今どのようなデータを出力しているかを出力してくれる SYNC というチャネルがあり LVDS としてはクロック1レーン、データ5レーンを受信することになります。
そしてこの値はSPIからレジスタを操作して順に起動していくと、デフォルトではトレーニングパターンとして 10’h3a6 というコードを繰り返し出してくれるようです。トレーニングパターン自体をSPIから別のコードにすることもできるようですが今回はこのまま使います。
720Mbps というのは決して遅くはないですが、基板設計がしっかししていれば4本チャネル個別調整するほどでは無さそうです。
なお Spartan-7 は LVDS を受信するための内部の100Ω終端を持っていますが、これはI/O電圧が2.5Vの時に使えます。今回私は3.3Vのバンクで受信する必要があったためFPGA外部に100Ωの終端抵抗を実装したボード設計としました。
Spartan-7 は受信したクロックを BUFIO でサンプリングクロックとして利用し、BUFR で分周クロックを作ることで、PLL や MMCM を使わずにクロック同期のSERDES受信回路を作ることができるようになっています。UG472 から引用した図が下記です。

各I/Oブロックの中のISERDESのサンプリングクロックにBUFIOからのクロックを使い、SERDES後の分周クロックにBUFRからのクロックを使えます。BUFRは1~8までの分周機能があるのですが、今回 DDR なので 5分周すれば丁度 1:10 にSERDES した際の 72MHz を生成できます。
この際、クロック入力の I/O ブロックの中にある IDELAYE2 ユニットを通すことで、クロックに遅延を加えることができます。
トレーニングパターンとして 10’h3a6 が安定してとれる遅延量を探索したところ0だと稀にダメで、1~ 15 個の間の遅延タップ挿入でトレーニングパターンが受信できました。当然ながらマージンが最大となる中央の 8 に設定しました。
シンクコードを判定して AXI-Stream に変換する
映像信号には映像部分の他に、垂直ブランキングであったり水平ブランキングなども含まれます。また多くのイメージセンサーでは黒レベル補正の為に、物理的に黒塗りされた画素領域(Optical Black)の読み出しが含まれるものも多いです。
(今回は使っていませんが PYTHON300の場合は複数のROI設定も可能なようです)
これらの今出ているデータが何を示すかは、イメージセンサーによって仕様が異なり、垂直同式信号や水平同期信号を別の専用線で伝えるようなものや、映像データと同じ信号線の中にエスケープシーケンスのように専用のコードを埋めるものもあります。
PYTHON300 の場合は 先に述べた SYNC 用の専用のLVDSチャネルから今データチャネルに何が出ているかを示すコードが出力されるようです。
データシートにも下記のようにいろいろ記載されています。

とはいえここまで動けば実機でも試せますので、論よりRUNで実際にILAを埋めて覗いてみると結果として下記のようになるようです。

なんだかフレームエンド信号がデータシートと違うような気もするのですが勘違いでしょうか?
こういう場合、実機を見るとデータシートの読み方を間違えていたのに気付けるケースや、データシートの誤記を見つけてしまったりもあるので、すぐに実機で試せるFPGAの良いところかと思います。
なお私はこの信号を早々に AXI-Stream に変換してしまいました。AMD(Xilinx) では、映像信号を AXI-Stream で扱う IP が多く、AXI-Stream にすると何かと便利です。説明はこちらなどにありますが、要は valid/ready でハンドシェークしつつ、フレームスタートを tuser、ラインエンドを tlast で示すようにしています。
valid/ready のハンドシェークは、FIFOでクロックを載せ替えたり、AXIバス経由でメモリと読み書きする際にも便利です。
画素を並び替える
これでやっと映像信号になったかと思いきや、さらにPYTHON300固有の処理として画素の並べ替えの必要があります(これはおそらく高速度撮影向けにROIをラインだけでなくカラムを制限した場合でも読み出し面積に応じてフレームレートを上げられるように工夫された結果ではないかと予想しています)。

当然データシート通り入れ替えればいいのですが、ここはFPGAらしく実際に並び替え前の状態で絵を撮影してみて確認しながらコードを書くことにしました。
AXI-Streamまで持ってこれたあたりで、既存のIPなどを使って画像をメモリに溜めて何とか読み込んで表示するようなことができるようになります。
ここは本記事では解説しませんが、「イメージセンサーの制御はしたことが無いが、HDMIなどの画像受信はやったことがある」みたいな方は、きっと手元に画像を表示するためのRTL資産がある事でしょう。そうでない場合でも大きな「FPGAを持ってきて信号をILAで数ライン溜めて、CSVに保存して Python で映像化」とか、「HDMIで出力してUSBビデオキャプチャで映像化」とかいろいろテクニックがあります。
話を本題に戻すと、とにもかくにも実際の映像を見ながら並び替えを試行錯誤することが出来ました。当然これがASIC設計なら一発勝負となるわけですが、FPGAだとトライアンドエラーを繰り返すことができるのでデバッグがとても楽です。
露光タイミング制御
この辺りからが FPGA でイメージセンサを制御する醍醐味になってくるのですが、多くのグローバルシャッターイメージセンサーでは露光タイミングをFPGAから制御することができます。

- ロボットの位置や、別のセンサーが反応した瞬間など外部タイミングでシャッターを切る
- 逆にシャッタータイミング時のロボットの位置や他センサーの値を画像に紐づけて保存する
- 露光タイミングの中で照明やレーザーやパターン照射などを動的制御する
- フレームレートや露光時間に変調を付けて特殊撮影をする
などの応用ができます。
例えば、回転しているプロペラを避けて隙間だけでシャッターを切るとか、きまったブレードだけを移し続けるとかも可能です。
ロボットなどで、シャッターを切った時刻のサーボモーターのエンコーダー値を記録しておけば、いつどこでどういう状態を写した画像かわかるので、動きながら補正を行うのに活用できます。
他にも照明と連動させることで、マルチスペクトル撮影をしたり、照度差ステレオ撮影や、場合によってはパターン投影を行ってストラクチャードライト計測なども可能です。
これらはグローバルシャッター+FPGAカメラの応用の賜物です。
特にFPGAで画像認識まで行う場合、コマ落ちすることもなく毎フレーム決まった時刻に処理結果を出力することもできますので、画像処理結果を他の機構のフィードバック制御に使う事もできます。特にPYTHON300は少しROIサイズを絞れば1000fps撮影以上の計測も可能ですので、多くの用途に利用可能です。
おわりに
結構長文になってしまいましたが如何だったでしょうか?
折角RTLを覚えたのでRTLでしか出来ないプログラミングをしてみたい方 イメージセンサー+FPGAの構成を自分で作ったり、そういう基板を買ってきたりして、自分だけの特殊素撮影制御のできるマシンビジョンカメラに挑戦してみるのも楽しいと思います。
特にイメージセンサーは出力されるデータ量が膨大ですので、FPGAで処理するのにとても向いています。
イメージセンサーのデータシートを読み解きながら、時には書いてないことを自己解析しながら、プログラミングを行う事は大変なことではありますが、それだけ真似するのが難しいことでもあります。
まだ誰も見たことが無いものを見てみたいから、そのためのカメラを自作するというのは新規性のある研究開発の第一歩になります。
案外世の中には「ある刺激を外から与えて、特別な波長を当てて、ある一瞬でシャッターを切るという事を繰り返して重ねると初めて見えてくるもの」なんて沢山あります。
RTLとFPGAで、新しいプログラミングの扉が開くことを期待して、終わりにしたいと思います。






コメント