SSブログ

HDMI-TXコア作った [IPコア]

あけましておめでとうございます。

ずーっと昔(→CycloneIII/IVでHDMIを映したい)に、HDMIへの対応をやりたくてそのままになってたものを、諸般の事情もあって重い腰を上げて作ることにしたのが先月の話です。

これまで手をつけずにきたのは、既にIPコア化していたDVI-TXで実用上は充分であり、またHDMI-TX化によって増加するロジックリソース分(特にパケットフレームの制御)がどうしてもデメリットとしてついて回っていたからですが、有り体に言うとめんどくさかったからです。

ところが最近ではDVI信号を受け付けないHDMIシンク機器(LCD変換やキャプチャーなど)が増えてきまして、これはいよいよどうにかしないとマズイという状況になりました。
実際のところ必要だったのはHDMI信号として成立させるための最低限の部分で、それ以上の部分は当面は不要だったのですが、この際ということで一念発起してオーディオストリームまで実装したわけです。

2023-01-08_04h11_30.png
Tweetへ

微妙にしょぼい見た目ではありますが、これでHD720pのxvYCC.709画面モード(ただしカラーバー信号はBT.709の範囲なので拡張色空間の発色はしてない)で44.1KHz/24bitステレオの再生ができています。

DVI-TXでは350LEほどだったリソースは、最終的には970LE+2M9kと3倍弱に膨れ上がってしまいましたが、合成オプションを設定してDVIモードでは330LE、必要最低限のLiteHDMI(Nullパケットのみ送信)モードでは380LEと、これまでの要望も満たせて納得の成果になりました。満足。

リポジトリはこちら
Github osafune/hdmi_tx

Intel Cycloneファミリ(III, IV, V, 10LP)とMAX10ファミリ、GowinのGW1NファミリとGW2Aファミリに対応しています。

FM音源DAC [IPコア]

ありがたいことにFM音源DACの資料にちょくちょくアクセスがあるようで、そのたびにGoogleDriveの承認を出すのも手間なのでPDFファイルをGitHubの方に移動しました。

FM音源DAC出力フォーマット

あと、DAC側をFPGAで組みたい奇特な方向けに、音源から出るDAC信号を模擬するHDLのリポジトリも上げておきます。

FM/PCM test signal generator

VHDLで飽和演算 [IPコア]

前にVHDLのリダクション演算を書いたので、それを使った飽和演算処理を紹介。
HDLで飽和演算をしたい場面はちょくちょくでてくるものの、完全にパラメタラブルに書くのは以外と面倒くさい。

ざっくりと処理条件を書くとこうなる。
入力データをd、出力データをyとし、それぞれのビット幅をn,m、n>mとする。

(1) 正の最大値への飽和
d[n-1](MSB:符号ビット)が'0'、かつd[n-2:m-1]の全てのビットが'0'でない場合。
y[m-1] <= '0', y[m-2:0] <= 全て'1'

(2) 負の最大値への飽和
d[n-1]が'1'、かつd[n-2:m-1]の全てのビットが1でない場合。
y[m-1] <= '1', y[m-2:0] <= 全て'0'

(3) 飽和なし
y <= d[m-1:0]

VHDLでは比較演算子でオーバーロードされるので、任意長ベクタ型がゼロかどうかの比較は簡単にできるものの、即値で任意長のビット集合を生成するのにかなり鬱陶しいことをやらないといけない。
故に比較するビット幅がパラメタラブルなものは鬼門なのだけど、ここでリダクション演算を使うとすっきり記述できる。
例えば

or_reduce(A) = '0'

とするとAの全てのビットが'0'かどうかを判別できる。同様に、

and_reduce(B) = '1'

とするとBの全てのビットが'1'かどうかを判別できる。
そうすると、まとめてこんな感じで書くことができる。

y <=
  (y'left=>'0',others=>'1') when(d(d'left) = '0' and or_reduce(d(d'left-1 downto y'left)) /= '0') else
  (y'left=>'1',others=>'0') when(d(d'left) = '1' and and_reduce(d(d'left-1 downto y'left)) /= '1') else
  d(y'range);

結構長ったらしい記述だけども、注目すべきはマジックナンバーが出てこない点。つまり、これはdとyのビット長を自動的に参照して飽和演算回路を生成してくれる。
こんな感じてVHDLのアトリビュート演算子はめっちゃ便利だから皆使おう。


ちなみに飽和演算のキモは出力部以外のビットが符号値のみで構成されているかをどう判断するかである。
なので飽和条件は以下のようにもうすこし詰めることができる。

(1) d[n-1:m-1]の全てのビットが同値の場合は飽和無し
y <= d[m-1:0]

(2) それ以外の場合は符号ビットから最大値を生成
y[m-1] <= d[n-1], y[m-2:0] <= not d[n-1]

任意長ベクタの全ビット値が同じかどうかは、リダクションORとリダクションANDの一致をとれば判別できるので

y <=
  d(y'range) when(or_reduce(d(d'left downto y'left)) = and_reduce(d(d'left downto y'left)) else
  (y'left=>d(d'left),others=>not d(d'left)); 

と書ける。こちらの方がソースリストはすっきりする反面、比較条件のパスが複雑になるので必ずしもロジックが減るわけではない、という点には注意。


HDLソースに制約を埋め込む [IPコア]

QuartusPrimeではソースファイル側にハードウェアやタイミングの制約を埋め込むオプションがある。
一番よく使うのは、クロックドメインをまたぐレジスタのタイミングパスを切る指示。
これは一般的なフローではSDCファイルに記述するが、どのレジスタがタイミングを無視していいかなんてのはソースに一緒に書いた方がわかりやすい。

記述はHDLの中にaltera_attribute属性の文字列を埋め込むことで行う。これはQuartusへ合成指示を渡すオプションで、qsfファイルに記述するテキストと基本的には同一。
‥‥が、Verilogではアトリビュート記述は一行に納めないといけないので、複数行に分割しないとSDCに書き出すよりも遙かに見難いソースが出来上がるので注意が必要。

・Verilog-2001
(* altera_attribute = {"-name SDC_STATEMENT \"set_false_path -from [get_registers *sv_xcvr_pipe_native*] -to [get_registers *altpcie_rs_serdes|*]\"; -name SDC_STATEMENT \"set_false_path -to [get_registers *altpcie_rs_serdes|fifo_err_sync_r\[0\]]\"; -name SDC_STATEMENT \"set_false_path -to [get_registers *altpcie_rs_serdes|busy_altgxb_reconfig*]\""} *)


・VHDL
attribute altera_attribute : string;
attribute altera_attribute of rtl : architecture is (
"-name SDC_STATEMENT ""set_false_path -from [get_registers *sv_xcvr_pipe_native*] -to [get_registers *altpcie_rs_serdes|*]"";" &
"-name SDC_STATEMENT ""set_false_path -to [get_registers *altpcie_rs_serdes|fifo_err_sync_r\[0\]]"";" &
"-name SDC_STATEMENT ""set_false_path -to [get_registers *altpcie_rs_serdes|busy_altgxb_reconfig*]""
);


FM音源のDACインターフェース解析の更新 [IPコア]

以前やったFM音源のDACインターフェース解析(2015-11-06)のつづき。

どうもYMF262の波形がおかしいという話になり、実機を調べてもらったところ、fsが14.318MHz÷256ではなく14.318MHz÷288だった。
YMF262はDACとしてYAC512を接続するのだけども、このビット長が16bit×2なのでそれをデータフィールドと勘違いしてしまったようだ。実際には16bit長のデータの前に2bitのダミーがあり、全体では18bit×2のデータフィールドになっている。
でもデータクロックのデューティ比が25%とかいうのは一緒。あんまりピン遅延とかボード遅延とかシグナルインティグリティとかをとやかく言われなかった時代っぽい感じはする。

あと変態的な信号を発生するとして一部のスジでは名高いYMF297も実機を測定してもらったので、データとして追加した。一緒にYM2203も調べて貰えばよかったか‥‥。

DAC出力フォーマット
※9/26追記:YM2151のクロック極性が逆になってたので修正しました


あと、C140の動作クロックとDACクロックがいくつなのかご存じの方いましたら@s_osafuneまでリプお願いします‥‥<(_ _)>

system2.jpg

FM音源のDACインターフェース解析 [IPコア]

ちょっと必要になったので、各方面のご協力のもと各種FM音源のDACインターフェースフォーマットを調べた。
音源チップのレジスタや蘊蓄なんかは多数引っかかるのだけど、やっぱりソフトからもボードからも見えない部分の情報はなかなか出てこない。

まとめたのは、YM2151、YM2608、YMF262、YMF288の4つ。YM2203はフォーマットそのものはYM2151と同じなのだけど、モノチャネルなのでクロックが半分になってる可能性があるのだがよく分からなかった。

DAC出力フォーマット_20151106.pdf
FM音源のDACインターフェース解析の更新(2016-09-26)に続く

system2.jpg

Packet to Transactionコアの動作について [IPコア]

QsysにはSPIやJTAGを使ってAvalonMMペリフェラルを操作できるAvalonMMバスブリッジモジュールがある。

SPI Slave/JTAG to Avalon Master Bridge Cores(pdf)
SPI Slave to Avalon Master Bridge Design Example

これを利用すると、NiosIIや自作AvalonMMマスタを使わなくてもQsysの中身を自在に操ることができる。
特にリソースの少ないMAX II/Vにちょっとしたペリフェラルを組み込んで制御するとか、FPGAの中身にテストでレジスタ操作をしたいというときには非常に便利な機能なのだけど、どうにも資料が揃っていない。
なので、ちょっと調べてみた。


・トランザクションの本体

SPI Slave/JTAG to AvalonMM Bridgeは単体のコアではなく、物理層・パケット層・トランザクション層の3つのレイヤからなる。このうちAvalonMMマスタとして振る舞うのはトランザクション層のコア(Packets to Transaction)になる。

このコアが処理できるパケットはコマンドヘッダにより4種類ある。
実際にはリード/ライトの2種類に対してそれぞれシングルとアドレスインクリメントの2つがあるので4種類ということになる。
基本的には

CMD 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD)

という共通の8バイトのパケットを流す。ライトの場合はこの後に書き込むデータを続ける。
SIZEはデータのサイズを指示する16bit長のデータ、ADRはアドレスを指示する32bit長のデータで、この2つのフィールドだけはネットワークバイトオーダーで指定する。

ただしQuartusII 13.0時点で、SIZEフィールドはリード時しか使われておらず、ライト時は後続のデータをカウントして書き込みサイズを取得するような実装になっている。

(1)シングルライトパケット

0x00 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD) D0 D1 … Dn

を送る。DATAは4バイト以下でなければならない。バイトアドレスの若い順から並べる(※アドレスフィールドとはバイトオーダーが逆になる)
また、アドレスは書き込むデータ長が32bit境界を跨ぐような指定をしてはならない。
下のような4バイトのライトレスポンスを返す。

0x80 0x00 SIZE(U) SIZE(D)

データは32bit単位で書き込まれるので、デスティネーションにバイトイネーブル機能がない場合、32bitデータ幅が全て書き換えられてしまうことに注意する。

(2)インクリメンタルライトパケット

0x04 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD) D0 D1 … Dn

を送る。DATAは最長65535バイトまで可能。バイトアドレスの若い順から並べる(※アドレスフィールドとはバイトオーダーが逆になる)
アドレスは開始バイト位置を指定する。
下のような4バイトのライトレスポンスを返す。

0x84 0x00 SIZE(U) SIZE(D)

インクリメンタルライトではデータは自動的に32bitにアラインメントされて書き込みが行われるため、これでレジスタに連続書き込みをする場合は、バイト単位でデータを送っていても32bit単位にまとめられて書き込みが発行されることに注意しなければならない。
特に非アライメントの転送をする場合にデスティネーションがバイトイネーブル機能を持っていないと、余計な部分まで書き込みがされる。

(3)シングルリードパケット

0x10 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD)

を送る。SIZEは読み込みバイト数で1,2,4のどれかを指示する。アドレスは読み込みバイト数が32bitアライメントを跨がないようにする。
下のようなリードレスポンスを返す。

SIZE = 0x0001の場合 : D0
SIZE = 0x0002の場合 : D0 D1
SIZE = 0x0004の場合 : D0 D1 D2 D3

AvalonMMの仕様として、リードは必ずデータビット幅(32bit幅)単位で行われるため、リードで状態が変わるソースに対してバイト/ハーフワードのリードをした場合、読んでいない部分のレジスタもリード扱いになるので注意すること。

(4)インクリメンタルリードパケット

0x14 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD)

を送る。SIZEは読み込みバイト数で65535バイトまで指定。アドレスは開始バイト位置を指定する。
下のようなリードレスポンスを返す。

D0 D1…Dn

開始アドレスからのバイトを返す。ただしシングルリード同様に、内部では32bit単位のリードを行っているため、アライメントに合わないアドレスあるいはデータ数を指示した場合、指定されていないアドレスのデータバイトをリードする。
リードによって状態が変わるソースにアクセスする場合には注意が必要。



パケット層と物理層のバイトエンコードはデータシート見れば分かるので省略。

Packet to Transactionの動作はなかなかよく出来ているので、結構トリッキーな使い方もできそう。
実はインクリメンタルとシングルの差はアドレスのカウントアップをするかしないかだけで、ステートマシンは同じなので、シングルライト/リードのパケットに32bit以上のデータを付けても動作するし、32bit非アラインメントから32bitデータの読み書きをしても、一応2つのトランザクションに分割してアクセスする。

ただしアドレスは増えないので非常に怪しいデータが書かれる。ついでに、シングルリードで非アラインで複数リードをすると、2回目以降のバイトイネーブルが正しく動かないようだ。

シングルリード/ライトで連続データを扱うと、うまくすればFIFOなんかの揮発性ペリフェラルから一度にデータを転送できるが、はまるとドツボなので、デバッグの手段がさっと思いつかないレベルの人はやらない方が賢明ではある。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。