まずLTC2380-24とはなにか、です。これは現代の最新テクノロジーによる真のマルチビットADCです。オーディオ用としてはハイビットのマルチビットADCは完全に死滅しており、最後似有名なのはおそらくReference Recordingsが使用しているPacific Microsonics model twoでしょうか。
Lavry GoldのDACはマルチビットですが、ADCのAD122-96ですらデータシートを見る限りではDeltaSigma系に見えます。
http://www.lavryengineering.com/pdfs/lavry-ad122-96mkiii-manual.pdf
ということで現代では非常に珍しいチャレンジになりますね…と思ったら海外ではこれに真剣に取り組んでいる事例がありました。
結局、後発にはなってしまいましたが、FPGAを覚えるいい機会と捉え、FPGAを使ってLTC2380-24のデータ取得、そしてオーディオ用ADCとして使えるように、XLR入力、SPDIF出力という機能を実現してみたいと思います。
基板設計
このような感じです。見て分かる通り二層基板です。それなりのクロックを使うデジアナ混在基板かつ高額ICが乗るので本来なら4層で設計すべきなのでしょうが2層で進めてみたら比較的きれいにパターンが引けたのでこれなら大丈夫だろうと2層でスタートです。だめならあとから4層にします。
仕様としてはXLR入力左右2系統、その後2段アナログフィルタ、OPA1632で差動化してADCに入力しています。リファレンス電圧は推奨通りLTC6655-5と堅実な設計です。FPGAはCmod-A7という秋月で売っている手軽なものです。このFPGAは別プロジェクトでも使っていたのでちょうど良かったものです。
SPDIF出力はFPGAでもできるらしいですが最初からそこまでできる自信はないのでPCM9211の余剰品を使った実装になっています。I2CもFPGAでできるらしいのですが同じくこれも実績あるマイコン制御とします。余裕があればこのあたりもFPGAでできるようにチャレンジしてみたいところです。
FPGAで大きくつまづく
基板設計、部品実装、アナログ回路の性能テスト、全て問題なしです。ここまでは超順調でした。ここは自分の専門分野みたいなもので経験実績があるので全く問題ありませんでした。最大の問題はFPGAです!覚えたての頃は(まだ1ヶ月位ですが)verilogが普通のCPUの言語と違って並列処理やハイインピーダンスの概念があってなかなか難しいと思っていたのですが、本当の敵はverilogではありませんでした。
verilogを書くのは実は全然難しくありません。真に難しいのは書いたverilogがFPGAと親和性があり、ハードウェアでの実現性、ハードウェアでの制約、これらの条件をしっかりクリアしているか、このあたりが問われることです。verilogで書いたとおりに動かせるわけではないということをあとから知りました。
例えばですが次のソース、verilog的には問題がないようでそのままシンセサイズまでは通ります。でもインプリメンテーションで怒られます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
always @(negedge MCLK_DIV) begin i2s_bit_count <= 8'd31; bit_count <= 8'd0; flag <= 0; end always @(posedge MCLK_DIV) begin i2s_bit_count <= 8'd31; bit_count <= 8'd0; flag <= 0; end always @(posedge BCK) begin if(bit_count == 0) flag <= 1; end always @(negedge BCK) begin //32bit if ((i2s_bit_count >= 31) && (flag > 0)) begin i2s_bit_count <= 0; if (MCLK_DIV > 0) data <= IN_DATA_R; else data <= IN_DATA_L; end i2s_bit_count <= i2s_bit_count + 1; DATA = (data & (32'h80000000 >> bit_count)) > 0; bit_count = bit_count + 1; end |
これの何が問題かと言うと、i2s_bit_countを異なるalwaysで共有しているからです。これはマルチドライブエラーで怒られます。どうやら同時に動くalways間でのレジスタの書き込みは一つだけ、あとは読み込みしか許されていないようです。このような厳しい制約があることは知りませんでしたのでとても困りました。結局次のように同じalwaysで使い回す形でなんとかしました。本当はもっと良い書き方があると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
always @(negedge BCK) begin if (MCLK_DIV == 0) begin mclk_div0 <= 1; end if ((mclk_div0 == 1) && (MCLK_DIV == 1)) begin i2s_bit_count <= 8'd31; bit_count <= 8'd0; mclk_div0 <= 0; end if (MCLK_DIV == 1) begin mclk_div1 <= 1; end if ((mclk_div1 == 1) && (MCLK_DIV == 0)) begin i2s_bit_count <= 8'd31; bit_count <= 8'd0; mclk_div1 <= 0; end //32bit if (i2s_bit_count >= 31) begin i2s_bit_count <= 0; if (MCLK_DIV > 0) data <= IN_DATA_R; else data <= IN_DATA_L; end i2s_bit_count <= i2s_bit_count + 1; DATA = (data & (32'h80000000 >> bit_count)) > 0; bit_count = bit_count + 1; end |
(この書き方だとインプリメンテーションは通りましたが、結局内部信号の到着タイミングによって意図しないエラーになるのでやっぱりダメです!くれぐれも真似しないように)
verilogでは表現できてもFPGAでは実現できない…これが最大の難関です。試練はまだまだ続きます。
次はタイミングの問題で動作しない
上記の制約の問題はなんとかクリアしたのですが、今度はシミュレーションで動作できるのに実際にハードウェアに書き込むとデータ化けしてしまうというものです。データ化けは次のような感じです。
正常(といってもスプリアスが発生している)
データ化け、異常
これも内部タイミングの問題です。ここのところは正直良くわかっていませんがVerilogを書き換えるたびに動作が変わります。謎です。タイミング制約をしなければならないらしく、こちらのサイトを参考に設定してみたのですが、完全に解決ができているのかは不明です。
http://todotani.cocolog-nifty.com/blog/2016/12/vivado-constrai.html
FPGAは初心者なのでわからないことだらけです。Verilogを書くなんて基本中の基本、本当の敵はFPGAへの実装の部分にありました!Cのように書けば書いたとおり気を利かせてコンパイルしてくれるようなそんな世界ではなかったです。ハードウェアを想定し細かい部分は面倒を見てあげないといけません。適当に書いて適当に動くなんてことはありませんでした!
多分このあたりはハードロジックにもともと詳しい方なら問題ないのでしょうが、プログラム言語で上から下に順番で動く動作ばっかり書いてきた人にとってはあまりにも概念が違っていて毎日頭痛状態です。FPGA開発をやっていると普段使っていない部分を使っているのか、数時間でオーバーヒート状態です!
とまぁ愚痴はこのあたりにして、次です。
タイミング制約、ソースのレイテンシを全面的に見直し
結局Verilogはほとんど全部書き直しになりました。最初は後段で必要な信号、クロックは計算で算出していたのですが、これだとネストが深くレイテンシが長いロジックが生成されてしまうので、できるだけシンプル、並列、これで動作できるように変更しました。具体的には最初に必要なタイミングクロックを同時並行処理的に作ってしまって、あとはひたすらシンプルにそれを使って処理するというものです。
クロック生成部は以下の通り。美しくないコードのような気がしていますが初心者なのでこんなものです。こういうふうにお膳立てしてあげれば後段のロジックは最低限で済むというものです。必要なタイミングは最初に同時並行的に生成してしまおうということです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
always @(negedge MCLK98M) begin case (SEL) 4'd0 : begin bck_count_max <= 5'd1; lrck_count_max <= 11'd64; end 4'd1 : begin bck_count_max <= 5'd2; lrck_count_max <= 11'd128; end 4'd2 : begin bck_count_max <= 5'd4; lrck_count_max <= 11'd256; end 4'd3 : begin bck_count_max <= 5'd8; lrck_count_max <= 11'd512; end 4'd4 : begin bck_count_max <= 5'd16; lrck_count_max <= 11'd1024; end 4'd5 : begin bck_count_max <= 5'd32; lrck_count_max <= 11'd2048; end endcase end //I2S always @(negedge MCLK98M) begin bck_count <= bck_count + 1; lrck_count <= lrck_count + 1; if (bck_count >= (bck_count_max/2)) begin BCK <= 1; end if (lrck_count >= (lrck_count_max/2)) begin LRCK <= 1; end if (bck_count >= bck_count_max) begin BCK <= 0; bck_count <= 1; end if (lrck_count >= lrck_count_max) begin LRCK <= 0; lrck_count <= 1; end end //ADC always @(negedge MCLK98M) begin if (REF_SCK == 1) begin REF_SCK <= 0; end else begin REF_SCK <= 1; end if (busy_count >= COUNT_400n) begin ADC_BUSY <= 0; end if (busy_count > COUNT_400n) begin SCK_ON <= 1; bit_count <= bit_count + 1; end else begin busy_count <= busy_count + 1; end if (bit_count >= 6'd48) begin busy_count <= 0; SCK_ON <= 0; end if (lrck_count == lrck_count_max) begin ADC_CNV <= 1; ADC_BUSY <= 1; busy_count <= 0; bit_count <= 0; end else if(lrck_count == (lrck_count_max/2)) begin ADC_CNV <= 1; end else if(lrck_count == (lrck_count_max/4)) begin ADC_CNV <= 1; end else if(lrck_count == (lrck_count_max/4)+(lrck_count_max/2)) begin ADC_CNV <= 1; end else begin ADC_CNV <= 0; end end |
以下に現状のブロック図を貼ります。デバッグ用に無駄な入出力ポートがたくさんありますが、これがないと少しいじったときに原因不明のエラーになったときに原因が見えないので現状は習うより慣れろ状態でひたすらシミュレーションで内部レジスタがどうなっているのかを検証しまくるしかありません。
自分はかなり右脳派なので、理論よりイメージ派なんです。なのでブロック図を使った開発にしています。慣れている方はコードのみでルーティングするそうなのですが、個人的には接続がわからないので最初からブロック図です。
タイミング制約はこんな感じです。これで書き方が本当にあっているのか不明なのですが、これを設定したら動いたので多分あってます。FPGAは初心者かつ手探りなので鵜呑みにせず、参考程度でお願いします。
768kHzで平均化サンプリング、192kHz出力に成功
上記の対策によりなんとか動作しました。FPGAの基本動作がようやくできたので次はようやくスプリアスとノイズ対策です。こちらはアナログの問題なので一瞬で解決です。設計回路図のとおりに部品をしっかり実装したらスプリアスとノイズの問題はきれいに解決しました。このようにアナログは試行錯誤含めてもすんなり結果がでてとても楽です。FPGAはわけのわからない挙動が多くてやばいですw とはいえこれも経験値だと思うので今のうちにFPGAを触れるようになっておき、数年後にサクサク作れれば上々でしょうね。
一応補足として48kあたりに謎の成分がありますがXLR入力ケーブルを外すと消えるので原因は外来かGND相互接続あたりのように思います。基板自身から出ているものではありません。
ノイズフロア
1kHz sine THD歪み
こちらでも書きましたが歪はDAC起因の可能性があり、ノイズシェーピング残留成分と1/fノイズがないノイズフロアで結構きれいになりました。
ADC開発
LTC2380-24の768kHzサンプリング>192kHz平均化処理がうまく行きました。計測結果は以下のとおりです。eltaSigma ADCでよくあるシェーピングの残留成分はありません。またTHDの3次歪はDAC側にある可能性があります。 pic.twitter.com/Oq4h50ZPbd
— yohine(Innocent Key) (@yohine_ik) August 22, 2019
LTC2380-24で録音した音
このあと調子に乗って1.5MHzの動作にチャレンジしたのですが24bitを一度に取らないで8bitを3回に分ける処理を書いたらノイズまみれでまた動かなくなったので、とりあえず動く状態に戻して768kHz平均化による192kHzサンプリングによるADCの音声録音データを作成してみました。聞いてみてください。多分1.5Mhzでサンプルできたらもう少し良くなります。最初192kそのままで取ったものは素性は全然悪くないのですがちょっとだけ曇った音でした。
https://drive.google.com/file/d/1QHE9diJ3SSIYGLvyM45hDuS3Rjkd46f4/view?usp=sharing
ファイルは24bitの192kHzです。それぞれ、AK4497-DACからLynx HiloでADCしたもの、AK4497-DACからLTC2380-24でADCしたもの、PCM-501ESからLTC2380-24でADCしたものとなっています。それぞれゲインとタイミングは概ね合わせてあります。
昨今はTwitterでも音声の著作権がうるさくなっているようなので、今回は念の為私が編曲、ミキシングしている楽曲にしています。そのなかでも音質が良く、いろいろな速度と帯域の音が入り混じっているテスト向きだと思う曲を選びました。
この楽曲についてはこちらに紹介と解説を書いています。2曲め「陽だまり猫だまり」という項目以下です。今はオーディオの技術ばっかりやっていますが、これでも以前は音楽制作がメインでした…(今は昔)もう11年前の曲と考えると目眩がしてきますw
最後にもう一度書いておきますが、FPGA初心者なのでVerilogまわりは本当に適当なこと書いてる可能性があるのでくれぐれもご注意ください!
参考リンク:ブロック開発のための資料
http://nahitafu.cocolog-nifty.com/nahitafu/2016/08/vivadortlip-3d7.html
https://forums.xilinx.com/t5/Design-Entry/How-to-simulate-Block-design-in-vivado/td-p/865525
https://qiita.com/iwatake2222/items/b0f2ce7b4653bc6f32b9
https://qiita.com/s_nkg/items/f2928fb727238d14f23f
参考リンク:ノンブロッキング代入とブロッキング代入
https://qiita.com/rikitoro@github/items/7a2ee703182c3abd9f83
https://blog.goo.ne.jp/sim00/e/9b21e3d0c135f94a49a88e147e344ab5
こんにちは。SARADCの出力をIISに変換したりspdifの出力をするのであれば、vivadoよりも一つ前のise14.7で回路図入力した方が簡単だと思います。TTLで回路を作るような感覚で出来ます。クロック関連と、ucfのタイミング制約の書き方に関する資料は読む必要がありますが。
現物の基板があるのでシミュレーションの必要はなく、実際の基板で動作確認できます。変更のたびに動作が変わるような事はありません(タイミング制約が正しく入っているならば)。Xilinxの場合、高位合成を一番に考えているようで、回路が大規模ならばそれで良しですが、小規模になるとvivadoは適さないでしょう。
但し、iseは基本的にspartan6専用です。お使いのartixはダメです。秋月でもspartan6の基板はあるようですが、spartan6ならQFPもありますのでそのまま基板に実装した方が楽でしょう(xc6slx4とか xc6slx9)。
情報ありがとうございます。簡単な回路を合成するなら古いバージョンのほうが良かったのですね。まだまだわからないことだらけですが、FPGAはわからないと教えてくれる方がいてとても助かります(実は他にも記事を公開してからご連絡いただいてたりします)
とりあえず今のところはとても簡単な機能しかやっていないので、Artixはオーバースペックだったかもしれません。基板にするなら絶対にBGAよりQFPがいいですね。今後FIRフィルターにもチャレンジしてみようと思っているところです。せっかくここまで覚えたのでもうちょっと頑張ってみたいと思います。まずは1.5Mhzサンプリングを成功させるところからになるのですが、最近は別の作業をしているので手を付けるのはちょっと後になりそうです。次のFPGAを使ったプロジェクトではもう少し楽な形を検討してみたいなと思っています。
如月さんの記事もいつも見ています。今回の企画はそれをみてというわけではなくてもっと前から構想があったものをやっと手を付けた形です。同じような考えを持っている方がいてとても感激したのを覚えています。マルチビットデルタシグマDACの件もそうです。最近は1bitにシフトしたようなので今後の展開も楽しみにしています。