FPGAボード(Sipeed Lichee Tang Primer)には、SDRAMが搭載されていることが1つのセールスポイントになっていました。
前回、FPGAボードのSDRAMを使うため、拡張や新規設計が必要なユニットの詳細設計を終了しましたので、作業としては終わっているはず、でした。まだ、結構大きめな落とし穴が開いていたとは……。でも、SDRAMを使うテーマは今回でようやく完結出来ます。
では、続きをご覧ください。
必要な回路の設計が済んで、TD IDEでコンパイル(論理合成・配置配線)したり、STAかけてみたりしたところ、どうも、SDRAMマクロに対してタイミング制約がかかっていないように見えました。
STAに全くレポートが上がってこないことから、SDRAMマクロに接続したクロックに対して、CTS(※)もかかっていない様です。つまり、SDRAMがクロックツリーに含まれていません。
※自動でクロックツリーを適切に生成するツールをCTS(Clock Tree Synthesis)と言います。CTSによって、世界中のフリップフロップ(F/F)が同じクロック立ち上がりエッジタイミングで動作できるので、同期設計が成立します。TD IDEでコンパイルすると、自動的にCTSが実行されています。
AD変換器マクロを使った時には、PLLで作った16MHzクロックをAD変換器マクロ本体と、制御回路(afeifブロック)に接続していました。この時は、ちゃんとAD変換器マクロがクロックツリーに含まれていたため、同期設計が成立していました。
SDRAMマクロがクロックツリーに含まれないということは、FPGA内部のSDRAMコントローラSDRAMC8Mユニット内F/Fと、SDRAMマクロへ供給するクロックの位相差を補償できないという事になります。これでは動くはずがありません……。
他にもクロック周りに起因する怪しげな挙動が見られ、少々デバッグに苦慮しました。以下、クロックツリーにまつわるドタバタの顛末をご報告いたします。
当初、PLLを使ってSDRAMマクロおよびSDRAMコントローラSDRAMC8Mユニットに供給する96MHzクロックclksdcを生成、Moscoviumマイコンの24MHzクロックclkと位相補償した上で、clksdcに180°ほどの位相シフトを設定していました。位相シフト量は、STAの結果を睨みながら、最適な値に調整する予定でした。
ところが、うまくSDRAMにアクセスできず、暫く嵌まってしまいました。デバッグしたところ、何やらSDRAMが全く動作・反応していない様子。もしかして、クロックが停止しているのではないか?PLLが怪しい。
そこで、PLLを使わず、24MHzのクロックをSDRAMマクロおよびSDRAMC8Mユニットに接続して試したところ、あっさり動作を確認できました。
同じ動作となる様に、PLLで24MHzクロックを生成し、SDRAMマクロ/SDRAMC8Mユニットに接続したところ、やっぱり全く動作しません。PLLがおかしいことが確定しました。少なくとも、イメージした期待通りのクロックを出力していない。
AD変換器の時にもPLLマクロは使っており、その時には普通に動いていました。今回と何が違うのか? → 今回は位相シフト機能を使いました。それが問題?
そこで、位相シフトせずにPLLマクロを生成したところ、これもまた、SDRAMマクロ/SDRAMC8Mユニットの動作を確認できました。
TD IDEで生成するPLLマクロの位相シフト機能にバグがあるみたいです。しょうもな〜。
バグだと判れば、PLLマクロの位相シフト機能を使わなければ良い話なので、切り替えて別案(次節)を考えてみました。
24MHzクロックclkと、96MHzクロックclksdc間の位相調整(位相シフト)は、必ず必要になると思われます。ですが、PLLの位相シフト機能は使えないことが前節のデバッグで判りました。
ならば、自前で位相シフトするしかありませんね。
位相シフト分周器の目標仕様:
この位相シフト分周器は、384MHzのクロックclkmul4で動作するスピードが必要です。
96MHz×4逓倍クロックclkmul4の両エッジを使うので、調整できる位相差の解像度は45°です。もっと細かく、22.5°単位で調整するには、×8逓倍クロック768MHzが必要になります。ただ、384MHzでも十分高速な上、それ以上の周波数となると、ちょっとした論理でもSTAでタイミングバイオレーションが発生しますので、これ以上周波数を上げるのは無理じゃないでしょうか??
同じ理由で、SDRAMマクロ/SDRAMC8Mユニットを駆動するクロック周波数を96MHz以上に上げることは困難でしょう。
RTLファイルmcoc_comn.vのmcoc_sdramモジュール内に位相シフト分周器のRTLが含まれていますので、ご確認ください。
位相シフト+分周後の出力クロックは、clksdc_r_Xと言う名前にしてあって、Xはclk〜clksdc間の位相差45〜360[°]の45°刻みです。使わなかった後ろの方のクロックは、どこかの最適化ステップで論理が消去されるはずですので、RTLとしては全セット揃えてあります。必要なクロックだけ使って、残りはそのまま放置してください。
24MHzクロックclkと、96MHzクロックclksdc間の転送は、同期設計にしなければなりません。AD変換器の時の様に非同期設計にしてしまうと、行きと帰りに2回づつF/F受けが必要となって、スピードが出ないためです。
下図の通り、データ転送モデルを設定して、これに従って回路を設計しました。
位相差の固定された異クロック間のデータ転送モデル:
上述の通り、SDRAMC8Mユニット内のphase,isync,osyncを生成する回路は、clk↑とclksdc↑が一致してしまうようなタイミングでは、正常動作できません。ただ、clk〜clksdc間に明示的に位相差を持たせることで同期設計するのが目的なので、clk↑とclksdc↑が一致してしまうようなタイミングでは使いません。つまり、特に問題とならない制限です。
世界中のクロックは、同じクロックの立ち上がりエッジで動作しないと同期設計が破綻します。
フリップフロップ(F/F)のクロック端子には、「クロック端子だよ」と言う属性(以下、仮に「クロック属性」とする)が付いていて、CTSツールがF/Fのクロック端子をリーフ(葉)とするようにクロックツリー(木)を生成してくれます。
ところが、SDRAMマクロのクロック入力には、クロック属性が付いていない様です。少なくとも、96MHzクロックclksdcのツリーには含まれていませんでした(接続したのに)。
何とか、SDRAMマクロをクロックツリーに組み込むような設定ができないものか?と調べてみたのですが、その方法を見つけることはできませんでした。実際、そのような方法があるのかどうかも不明です。
CTSの恩恵を受けられればGoodだったのですが、仕方ないですね。これも一種の仕様バグだと思うのだけど……。Anlogic社(FPGAチップとTD IDEのメーカ、中華企業)のサポートは当てにできないので、バグを回避するしかありません。
CTSが使えない以上、SDRAMマクロのクロックと、SDRAMコントローラSDRAMC8Mユニットのクロックを同一エッジで動作させるのは困難です。こういう場合は、2つのクロック間に明示的な位相差をつけて、同期設計することになります。
SDRAMマクロに供給するクロックをclksdrとして、SDRAMC8Mユニットに供給するクロックclksdcと分離、明示的に位相差をつけて設計します。つまり、24MHzクロックclkと96MHzクロックclksdc間で検討したのと同じことを、clksdcとclksdr間でも実施します(次節で検討します)。
まず、検討の前提となるクロックツリー図を以下に示します。
厄介なのは、クロックツリーによる伝搬遅延時間(図中の記号でclksdc_delayやclksdr_delay)を制御できないことです。
伝搬遅延時間は、クロックの先につながるF/Fの数や配置などによって異なります。F/Fの配置は、配置配線する時のトライアルによって異なり、恐らく、乱数的なものを使って、最適な配置をトライアルしているので、配置配線を実行する度にF/Fの配置は微妙に変わります。このため、伝搬遅延時間も配置配線する度に異なる時間となります。
各々制御不可能なclksdc_delayとclksdr_delayを合わせて、2種類のクロックエッジをピッタリ同じ位相にそろえることはできないのです。
前節のようなケースでは、敢えて2種類のクロック間に位相差を持たせ、それを前提としたタイミングで論理回路を設計することで、回路動作を成立させる方法があります。
以下図の位相差を前提として、SDRAMC8Mユニットの論理を設計し、SDRAMマクロとのデータ転送を実現します。
上図に戻って、転送タイミングモデルについて解説します。
以上の転送モデルに従って、SDRAMコントローラSDRAMC8Mユニットの論理を設計しました。
以下リンクより、SDRAMを搭載したMoscoviumマイコンMCOC115SD0408のデザインをダウンロードできます。FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
以下にタイミング制約ファイルの内容を参照して簡単に説明しておきます。
タイミング制約ファイル:MCOC_sdram.sdc
#Created Clock
create_clock -name clk -period 41.6 -waveform {0 20.8} [get_ports {clk}]
create_clock -name clkmul4 -period 2.6 -waveform {0 1.3} [get_nets {sdram/clkmul4}]
create_clock -name clksdc -period 10.4 -waveform {5.2 10.4} [get_nets {sdram/clksdc}] # X=180
create_clock -name clksdr -period 10.4 -waveform {2.6 7.8} [get_nets {sdram/clksdr}] # Y=90
#Set Multicycle Path
set_multicycle_path -setup -end -from [get_clocks {clk}] -to [get_clocks {clksdc}] 2
set_multicycle_path -hold -end -from [get_clocks {clk}] -to [get_clocks {clksdc}] 2
set_multicycle_path -setup -start -from [get_clocks {clksdc}] -to [get_clocks {clk}] 4
set_multicycle_path -hold -start -from [get_clocks {clksdc}] -to [get_clocks {clk}] 4
clksdcの波形設定は、位相シフト分周器から出力した時点のルートクロック波形を設定します。
上の記述では、clk→clksdcを180°、clk→clksdrを90°遅らせた波形が設定してあります。結果として、clksdc→clksdrは90−180=−90°の位相差(マイナスは位相進み)です。クロック間位相差を変更した場合には、それに合わせて、この設定値を修正してください。
位相シフト分周器に入力するclkmul4にも、4×96[MHz]のクロックを設定します。
96MHz ⇔ 24MHzの転送はマルチサイクルパスになっていますので、その設定も入っています。
以下に、コンパイル(論理合成・配置配線)結果をまとめておきます。
SDRAMのテスト用プログラムを以下にリンクします。
SDRAMテスト用プログラム:test_sdram.asm
※アセンブルに必要なインクルードファイルは、こちらからダウンロードしてください。
アセンブルしてFPGAボードに書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
シリアルモニタコマンド仕様:
以下、実験中の様子です(写真は使い回し)。
実験を進めましょう。
実験サンプル:【 】内はシリアルモニタからの送信データ
DBG: sdram initialization start
DBG: sdram initialization time: 0x000087df[us]
RDY
SDRAMの起動時間は約35ms(≒0x87df[us])でした。想定より長いイメージです。SDRAMC8Mユニットが明示的に待っているのは0x5000サイクル/96MHz≒210μs(SDRAMのパワーオンシーケンス、安定時間>200μs)だけなので、ほとんどの時間はPLLの発振安定時間です。
【march】
DBG: write all 0
DBG: read 0, write 1
DBG: read 1, write 0
DBG: read all 0
OK: marching finished: 0x000021dc[ms]
SDRAM全面(8Mバイト)に対して、マーチングテストを実施しました。マーチングテストのアルゴリズムについてはこちらに説明があります。
上実験サンプルのメッセージより、テストに要した時間はおよそ8.7s(0x21dc[ms])でした。
SDRAMコントローラSDRAMC8Mユニットの動作確認ができれば良いので、マーチングテストがパスした以上、まず、正常に動いたと考えて良いでしょう。
更に続けます。
実験サンプル:【 】内はシリアルモニタからの送信データ
【wra,aaaa】
OK: write all=0xaaaa
ここで何も操作せずに、数分⏳放置します。
【rda,aaaa】
OK+ elapsed time=0x0006b474[ms]
OK: read all=0xaaaa
今度実行したのは、リテンションテストと言います。
SDRAMの記憶原理はダイナミックなので、何もせずに放置しておくと、時間の経過によって記憶したデータが消えてしまいます。データが消えてしまう前に読み出して/再書き込みする処理をリフレッシュと言います。
SDRAMC8Mユニットのリフレッシュ機能が正しく実装されていることを確認するため、【wra】コマンドで適切なデータを書き込んだ後で一定時間放置し、その後【rda】コマンドで先ほど書き込んだデータが読み出せることを確認しました。
上の実験例では、約7分(0x06_b474[ms])放置後、書き込んだデータがエラー無く読み出せました。OKです。
実際に回路をFPGAボードに書き込んで、「clk⇔clksdc間転送モデル」と「clksdc⇔clksdr間転送モデル」で検討したクロック間位相差(clksdc_r_Xとclksdr_r_Y)を変化させ、動作を検証しました(下表)。
clk〜clksdc間位相差(clksdc_r_X)の方は、タイミング的にそれほどシビアではないので、X=180°および225°の二点で確認しておきました。主に、clksdc〜clksdr間位相差(clksdr_r_Y)を変化させて動作確認するのが目的です。
clksdc_r_X[°] | clksdr_r_Y[°] | Y−X位相差[°] | マーチング |
180 | (360=)0 | −180 | × |
180 | 45 | −135 | ○ |
180 | 90 | −90 | ○ |
180 | 135 | −45 | ○ |
180 | 180 | 0 | ○ |
180 | 225 | 45 | × |
225 | (360=)0 | −225 | × |
225 | 45 | −180 | ○ |
225 | 90 | −135 | ○ |
225 | 135 | −90 | ○ |
225 | 180 | −45 | ○ |
225 | 225 | 0 | × |
実際にFPGAボードに書き込んで、マーチングテストが正常動作(パス)したかどうかを○/×で記しました。
以下に補足・考察します。
以上より、SDRAMC8Mユニット用ルートクロックとしてclksdc_r_180、SDRAMマクロ用ルートクロックとしてclksdr_r_90を設定して、デザインデータページにアップしてあります(上表ピンク色セルの設定)。
位相シフト分周器と同じRTLファイルmcoc_comn.vのmcoc_sdramモジュール内に以下の様なクロックバッファが設置してあります。
// clock buffer
EG_LOGIC_BUFG sdcroot (
.i(clksdc_r_180), // Input
.o(clksdc) // Output
);
EG_LOGIC_BUFG sdrroot (
.i(clksdc_r_90), // Input
.o(clksdr) // Output
);
上側のインスタンス(sdcroot)がSDRAMコントローラSDRAMC8Mユニット用のルートクロックバッファ、下側のインスタンス(sdrroot)がSDRAMマクロ用のルートクロックバッファです。自分で位相調整しなければならない事態となった場合、ここに記述するクロック名(緑字部分)によって、位相を調整することが出来ます。
当方の実験結果(上の表)では、4タップ程度のパス範囲がありましたので、「ピンポイント」って程ではないみたいです。「必ず正常動作するポイントがどこかにあるはず」、と信じて調整してみてください。
楽しんでください(Enjoy and good luck!)。
FPGAボード(EG4S20BG256)のSDRAMは8Mバイトですよ。8Mバイトもあったら、UNIXなんて余裕で動いてしまいます(ただし、MMUが必須)。
16ビットのマイコンで8Mバイトなんて、一体何に使おう(1^−^;)?
PCやスマホのメモリに慣れてしまうと、8Mバイトなんて微々たる容量だけど、組み込み用途で16ビットマイコンに使わせようと思ったら、とてつもない大容量メモリなのでした。まだ、有用な使い道についてはアイディアがありませんw(1__)w。