FPGAボード(Sipeed Lichee Tang Primer)のAD変換器マクロを、オリジナル16ビットCPUコアMoscoviumマイコンに搭載して使って見ようと思います。
Moscoviumマイコンでは、SPIはソフトウェアSPIで使うことに決めたので搭載していませんが、今回搭載するAD変換器を加えて、UART、I2C、PWMタイマ、汎用I/Oポート、等、Arduino UNOで使われているATmega328に搭載されたI/Oユニットは粗方搭載できたこととなります。
FPGAボードやTD IDEなどのデータシートから読み取った情報をまとめておきます。
以下図は、データシートから引用したAD変換タイミング図です。
※TD IDEデータシートから引用
変換開始トリガ信号SOCをアサートすると、サンプリング4サイクル+変換12サイクル後に、変換終了信号EOCがアサートされます。変換時間は16サイクルです。
Arduino UNOのanalogRead()関数が約104μs(※)なので、それに比べるとすごく速いです。しかも、UNOのAD変換器は解像度が10ビットなので、その点でも高性能なAD変換器となっています。
※UNO(ATmega328)のAD変換器も、頑張ると約13μsくらいまでは高速化できます。
Moscoviumはと言うと、24MHzという決して速くはないマイコンなので、1μsで連続して変換終了されてしまうと、データのハンドリングが間に合わない恐れもあります。そのくらい速いって事です。
※1μsの間に、24MHzのクロックは24発しか入りませんので、Moscoviumの命令数にしてざっと24命令ですから、その間にポーリングして、I/Oレジスタからデータを読み出して、どこかのバッファ(RAM等)に書き込む、だけで精いっぱいです。片手間に他の処理をこなす余裕はありません。
Moscoviumマイコンに実装するための具体的な検討を進めます。
Moscoviumはクロック周波数24MHzで動作しています。AD変換器マクロの最高周波数は16MHzなので、分周するなどして、16MHz以下に落とさないと使用できません。
AD変換時間は16サイクルなので、16MHzを供給した時、最速の変換時間1μsを実現できます。24MHzを1/2分周して12MHzクロックを使う場合、変換時間は1.33μsとなります。ちょっと遅くなるだけなので、許容範囲です。
ただ、クロックツリーが課題なのです。
24MHzを分周して12MHzクロックを作ると、そこからクロックツリー🌲が張られることになります。
クロックツリー:
24MHzを1/2分周するのに使うF/Fは24MHzクロックツリーなので、世界中のF/Fと同じタイミングで動いています。分周して生成した12MHzクロックも、生成したその場所、つまりルートクロックとしては、24MHzクロックの立ち上がりエッジから、F/Fの出力遅延時間だけ遅れたクロックになっています。
ところが、そのルートクロックから、今度は12MHzクロックのクロックツリーが張られるため、12MHz世界のクロックタイミングを合わせるために、12MHzクロックツリーの伝搬遅延時間(clk12m_delay)だけ遅れます(下図)。
こうなってしまうと、24MHzクロックと12MHzクロックの位相関係を保証することが出来ないため、2クロック間の信号やり取りを「非同期信号」として扱うしかなくなってしまいます。
※上図で、「output_delay」だけは、セルライブラリの遅延時間で確定しますが、「clk_delay」と「clk12m_delay」は、先に繋がるF/Fの数や配置のみならず、配置配線のトライアル次第で変動するため、制御できません。
24MHzクロックと12MHzクロックの位相関係を保証するためには、PLLの位相補償機能を使う方法があります。が、どうせPLLを使うなら、12MHzなんて中途半端な周波数ではなく、AD変換器の性能を引き出すことのできる16MHzを生成して使いたいと思いました。
この場合、16MHz:24MHz=2:3の比となり、16MHzクロックの立ち上がりエッジは、24MHzクロックの立ち上がりエッジとも、立ち下がりエッジとも一致するタイミングが出来てしまいます(下図)。
つまり、PLLで位相補償しても、同期設計するのは困難なのです。今回は以下の通り対処します。
TD IDEのPLLマクロは、「Toolsメニュー ⇒ IP Generator」で生成することが可能で、最大4個まで使用可能です。以下図に今回使用するPLLマクロの設定画面(の要約)を示します。
「adc_pll16m」と言う名前で、上図のPLLマクロを生成してください。
※今回は、16MHzクロックと24MHzクロックの位相補償をせず、非同期設計することに決めたので、非2冪数の分周回路(2/3分周回路が必要)で生成しても良かったのですが、PLLを使って見ました。上の要約を見る限り、クロック入力24MHzを、一旦×42逓倍して1,008MHzを作り、これを1/63分周して16MHzを生成しているみたいです。
1回だけAD変換を実行して、変換結果をI/Oレジスタから読み出す場合には、変換時間約1μs、24MHzクロックでたったの24サイクルと非常に高速なので、変換開始してから変換終了までポーリングしてデータを受け取っても、それほど大きなオーバヘッドではありません。
一方、定期的にAD変換を実施して、外部アナログ量の変化を監視するような用途では、タイマなどの割り込みを起こし、割り込みサービスルーチン(ISR)内でAD変換を実施することになります。
こんなことで貴重なタイマリソースを使ってしまうのは勿体ないので、AD変換器自体にタイマを内蔵しておき、一定時間毎に自動的にAD変換を実行できるような機能を持たせようと思います。
この場合、CPU処理とは非同期に実行されるAD変換結果をI/Oレジスタから読み出すために、AD変換器から変換終了を知らせる割り込みが必要になります。
AD変換終了割り込みを実装して、割り込みコントローラINTCに接続しておこうと思います。
TD IDEで生成するAD変換器マクロでは、最大で8チャンネルのアナログ入力端子を扱うことが可能です。アナログ入力端子は、ある特定の端子に決まっていて、変更できません。こちらのFPGAボードピン配置表に於いて、「ALT」欄にADC0〜7と記した端子に固定されています。
AD変換器マクロを生成するIP Generatorウインドウで指定したアナログ入力端子(チャンネル)にデジタルポートを割り当てると、コンパイル(論理合成・配置配線)時にエラーとなります。デジタル/アナログ端子として同時使用は不可ということみたいです。
アナログ入力は最大8チャンネル使用可能なのですが、それによってデジタル端子が8本も減ってしまうのは勿体ないので、Moscovium用としてはアナログチャンネル数を4チャンネルとします(↓)。
上図の通り、TD IDEのIP Generatorを使い、AD変換器マクロを生成してください。
集積回路の設計は、基本的に同期設計です。非同期設計は「職人」の範疇で、素人が手を出すと、必ずバグを作り込み、思わぬ不具合に見舞われるからです。
ひっくり返すと、同期設計をしている限り、ツールのサポートを最大限受けることが可能なこともあって、まあ、多少のボロ設計はカバーしてもらえます。
なので、あまり非同期設計はしたくないのですが、今回の様に、「AD変換器の最大性能(変換時間1μs)を引き出すため」やむを得ず、非同期設計せざるを得ない場合には、細心の注意を払って進める必要があります。
まず、非同期信号を受け取る場合の注意事項は、「非同期信号は、自分のクロックで2回受けてから使う」ことです。FPGAボードの外部から読み込む信号をこのようにして処置することはこちらで解説済みです。FPGA内部の通信信号であっても、今回の様に16MHzクロックと24MHzクロック間で非同期通信する場合、同様に「2回受け」が必要となります。
16MHz→24MHzの場合も、24MHz→16MHzの場合も、両側で「2回受け」が必要となり、各々のクロックが非同期なので、こちらが送信した信号を、相手がいつ受け取ってくれるか、正確に把握することは困難です。従って、非同期設計における信号やり取りの基本は、「ハンドシェイク(握手)」になります。
以下、まずは、欧米での挨拶としての「握手」の手順を記載します。
握手の手順:
この所作を、信号のやり取りで実現することをハンドシェイクと呼びます。今度は、信号のハンドシェイクにフォーカスして、手順を示します。上の「握手」手順と対比してご確認ください。
こうすることで、確実にこちらの意思を相手側に伝えることができるのです。
I/Oレジスタの設定値やAD変換後の変換データなど、複数ビットから成るデータのやり取りも、ハンドシェイクを使います。
下図の様に、まず、相手に転送したいデータ(例えば12ビットAD変換結果、AD_DATA[11:0])を準備して確定します。
こんな具合に、非同期クロック間の通信は手間がかかって、かつ、遅いのですが、上の方式(ハンドシェイク)であれば、互いのクロックがどのような周波数であっても、常に通信が成立します。同期設計できない場合には、この様に非同期でも設計することは可能だ、と言う事です。
今回はここで一旦閉じておきます。次回、Moscoviumマイコンに搭載できるユニットへと詳細設計を進めます。