Tang Nano 9KでHDMI出力
目標
サンプルをコピペではなく、きちんと設計してHDMI出力をする
HDMIの概要
概要とはいっても、HDMIとは何かとかそういう話はさすがに省略します。
HDMIを出力するにあたり、仕様などの概要を確認します。
以前別記事([FPGA]液晶モニター遅延測定器を作る[ARTY Z7-20])の「HDMIの伝送方法概要」で取り上げましたので参照していただければと思います。
ざっくりいうと、
・(RGB各色8bitフォーマットの場合)1byteを10bitで送る。
・伝送路はRGB別に3経路ある。。
・1ドットごとの転送速度は、VESA DMTでPixel Clockとして定義されている。
となります。
HDMIはシリアル伝送で、1byte(3経路あるので事実上1dot)送信するための伝送速度は、1経路あたりPixel Clockの10倍となります。
最低限読むべきデータシート
DVI出力IPコア
Gowin DVI TX RX IP User Guide
https://www.gowinsemi.com/upload/database_doc/2720/document/654853e392d55.pdf
HDMI出力をするためのIPコアになります。音声を使わない限りDVIと共通のため、DVI用コアを使用します。
ちなみにDVIには音声を乗せる規格はありません。音声を乗せるためにはHDMI規格に対応する必要があるのですが、HDMI対応と言ってしまうと(少なくとも商用では)ライセンス料が必要となるため、無料で堂々と(商用利用ベースとして)公開するのは難しいのではないかなと思います。
ざっくりななめ読みすると、3.3.1章に使用したいIPのPort Listがあります。
・I_rst_n
リセット入力です。アクティブLです。
・I_serial_clk
シリアル信号送出用基準クロックです。前述のとおり、1ドット(rgb各ch1byte)=10bit×3経路で送信しますが、DDRで動作するようなのでピクセルクロックの5倍のクロックを入力します。
・I_rgb_clk
ピクセルクロックです。
・I_rgb_vs/I_rgb_hs
垂直/水平同期信号です。
・I_rgb_de
ピクセルデータイネーブルです。ブランキングを除いた実画像のときにHとします。
・I_rgb_r/I_rgb_g/I_rgb_b
実画像データです。
・O_tmds_clk_p/O_tmds_clk_n
HDMI出力クロックです。差動信号なので2本あります。
・O_tmds_data_p/O_tmds_data_n
HDMI出力データです。差動信号が3経路なので2×3=計6本あります。
また、3.5章にタイミング詳細がありますが、上記信号をVESA DMTを参考に適当に常識的な感じでpos edgeクロックで入れれば動きそうです。
なので、ピクセルクロックの5倍のクロックが最低限必要になるということがわかりました。
そこから、Tang Nano 9K(GW1NR)はスペック上どのような規格のHDMI信号を送信できるのかを調べます。
PLL
そこで、GW1NRのPLLはどのくらいまでのクロックを出力できるか調べます。
データシートはここから取得できます。
https://www.gowinsemi.com/upload/database_doc/1785/document/671b6f655ae08.pdf
http://cdn.gowinsemi.com.cn/UG286.pdf
UG286については、インストールフォルダに日本語ドキュメントもあります。親切ですね。
デフォルトであれば下記です。
C:\Gowin\Gowin_V1.9.10.03_Education_x64\IDE\doc\JP
こちらを見ると、GW1NRはrPLLというPLLが使用でき、その出力周波数は3.125MHz~600MHzとなっています。
なので、600MHzを5で割り、Pixel Clockは120MHzが限界ということになります。
Pixel Clockが120MHzだとどのくらいの解像度まで行けるかというのは、VESA DMTに記載があります。
https://glenwing.github.io/docs/VESA-DMT-1.13.pdf
1章のTable 1-1: Summary of Display Monitor Timings – Standards and Guidelinesという表に一覧で記載されています。
一番使いどころのありそうな1920x1080 60pはPixel Clockは148.5MHzなので対応できません。
ですので、次いで使い道がありそうな
・1366 x 768 60 Hz(RBのほう)
をターゲットにすることにします。なぜRBかは後述します。
設計する
PLL
まず必要なのはクロック系統です。
必要なクロックはピクセルクロックの72.000MHzとシリアル伝送用の72×5=360MHzです。
とはいうものの、ピクセルクロックはシリアル伝送用の5分周ですので、結局はシリアル伝送用クロックを作成すればよいことになります。
ここで、先ほどのUG286を読みつつ、rPLLの設定値を検討します。
GW1NRのrPLLは以下のような概要になっています。
ここで多少PLLの動作について説明します。
まず、TangNano9kでは、基準クロックとして27MHzの発振器が搭載されており、今回はそちらを使用するため、クロック入力には27MHzが入力されます。
次に、図中の要素について説明します。
・xxDIV
単純にクロックを分周するものです。
・PFD(Phase frequency detector)
位相比較器とも呼ばれます。二つの入力クロックの位相を比較する(変化点のずれを検出する)ものです。
が、PLLとして使う場合の動作の概念としては、二つのクロックの速度を比較するもの、と考えたほうが近いです。
仮に2つのクロックの変化点がどこかのタイミングで一致したとして、その次の変化点は2つのクロックの速度が異なっていれば必ずずれます。
それを検出して、2つのクロックの速度差を検出するようなイメージです。あくまでイメージであり、この説明は厳密には間違っています。
厳密にはPFDとVCOの間にLPF(ローパスフィルタ)という回路がありますが、今回の説明ではLPFも含めてPFDとします。
PFDは、どちらのクロックが早いかというものを比較して、その結果を(LPFを経由して)電圧としてVCOに渡します。
・VCO(Voltage Controlled Oscillator)
電圧に応じて出力周波数が変化する発振回路です。
さて、これらが組み合わさるとPLLはどのような動作をするかですが、まず考え方の簡略化のため、すべてのDIVの分周比を1(つまり素通り)と考えます。
また、VCO出力からPFDに戻る経路を、フィードバッククロックといいます。
VCOは初期段階では適当に発振します。それをPFDがクロック入力と比較し、クロック入力のほうがフィードバッククロックより遅かった場合、VCOの発振周波数をもうすこし遅くするように作用します。
逆に、クロック入力のほうが早かった場合、VCOの発振周波数をもう少し早くするように作用します。
その結果、ある程度時間が経った段階で、VCOの発振周波数とクロック入力の周波数が完全に一致します。これをPLLがロックしたといいます。
つぎに、DIVが1以外の場合を考えてみますが、IDIVはクロック入力周波数が変化するのと事実上同義なので無視します。
まず、FBDIVが4分周、ODIVが1分周(素通り)とします。
この場合、VCOの発振周波数を4分周した周波数とクロック入力を比較することになりますので、VCOの発振周波数がクロック入力の4倍となった状態でPLLがロックします。
つまり、クロック出力はクロック入力の4倍の周波数が出力されます。
さらに、ODIVも1以外の場合も考えてみましょう。
FBDIVが4分周、ODIVが2分周とします。
この場合、VCOの発振周波数がクロック入力の8倍となった状態でPLLがロックします。
そして、クロック出力はVCO出力の2分の1、つまりクロック入力の4倍の周波数が出力されます。
結局は先ほどのFBDIV=4、ODIV=1の場合と外見えは同じになります。
ここで、なぜODIVが必要になるかというところですが、VCOの出力クロックというのは仕組み上出力Dutyが正確に50%にならない場合があり、そのためにODIVが存在します。
ODIVで2分周以上をすることで、PLLがロックしている限り、必ずクロック出力のdutyは50%となります。
以上が、PLLの基本的な動作となります。
では、今回必要となるクロックは360.0MHzですので、それぞれのDIVはどのような値を設定する必要があるのかを検討します。
・・・が、IP Core Generatorで自動で計算してくれます。
CLKINに27.000MHz、CLKOUTに360.0MHzを設定し、Calculateボタンを押すことで自動計算してくれます。
IDIVはCLKINのDivede FactorのStatic値、
FBDIVはCLKFBのDivide FactorのStatic値、
ODIVはCLKOUTのDivide FactorのStatic値
に記載されます。
これ以外の組み合わせでも所望のクロックを作成することはできると思いますが、ツールでの自動計算値を推奨値として使うのがよいでしょう。
ちなみに、
GW1NR series of FPGA Products Data Sheet(前述)の、3.4.7章 PLL Switching CharacteristicsのTable 3-21 PLL Parametersに、
CLKIN:3~400MHz
PFD入力:3~400MHz
VCO出力:400~1200MHz
CLKOUT:3.125~600MHz
と定義されていますので、この範囲に収まるようにいい感じに分周比を選択する必要があります(ツールが自動計算してくれます)。
なので、今回は、
IDIV:3
FBDIV:40
ODIV:2
としてrPLLを使用します。
結局この設定でCLKOUTの周波数がどうなるのかパッと見でよくわかりませんが、UG286の5.1章に、
𝑓CLKOUT = (𝑓CLKIN ∗ FBDIV) ⁄ IDIV
と記載があります。
ちなみに1366x768のRBではないほうのタイミングはシリアルクロックが427.5MHzとなりますが、この周波数はこのPLLでは生成できませんので、RBを選択しています。
HDMI timing作成
HDMI信号(というか、アナログVGAの時代から同じ)のタイミングの考え方詳細については、
別記事([FPGA]液晶モニター遅延測定器を作る[ARTY Z7-20])の「HDMIの伝送方法概要」を参照してください。
とりあえず、ディスプレイにきちんと画像データとして認識してもらうためには、正しいタイミングで同期信号を作成する必要があります。
先ほどターゲットとした、「1366 x 768 60 Hz(RB)」は、VESA DMTに記載がありますが、
・ピクセルクロック:72.000MHz
・H Front/H Sync/H back:(14/56/64)CLK
・V Fromt/V Sync/V bask:(1/3/28)line
となっています。
なので、水平方向のカウンタ(MAX:14+56+64+1366=1500)と垂直方向のカウンタ(MAX:1+3+28+768=800)を用意し、そのカウンタを見てHSYNCとVSYNCとデータイネーブルを作成します。
画像データ作成
今回の本題ではないので、適当にカウンタの値をそのまま乗せます。グラデーション表示になります。
ちなみにデータが無効の期間にも適当なデータを乗せても問題無いようなので、特にデータイネーブルを見てマスクなどはせずに、そのままrgbデータとしてカウンタの値を乗せています。
作る
ソース
出来上がったソースは下記になります。
ソースの入力までの手順は、Tang Nano 9KでLチカを参考にしてください。
TangNano9KでHDMI出力
module top (
input wire rst_n,
input wire clk,
output wire hdmi_clk_p,
output wire hdmi_clk_n,
output wire [2:0] hdmi_data_p,
output wire [2:0] hdmi_data_n
);
wire grst_n;
wire clk_serial;
wire clk_pixel;
wire pll_locked;
wire hsync;
wire vsync;
wire dataen;
wire [7:0] data_r;
wire [7:0] data_g;
wire [7:0] data_b;
//グローバルリセット。rst_n入力時もしくはpll非ロック時はリセット
assign grst_n = rst_n & pll_locked;
rPLL #(
.FCLKIN (27),
.IDIV_SEL (3-1),
.FBDIV_SEL (40-1),
.ODIV_SEL (2),
.DEVICE ("GW1NR-9C")
) pll (
.CLKIN (clk),
.CLKFB (1'b0),
.RESET (~rst_n),
.RESET_P (1'b0),
.FBDSEL (6'b0),
.IDSEL (6'b0),
.ODSEL (6'b0),
.DUTYDA (4'b0),
.PSDA (4'b0),
.FDLY (4'b0),
.CLKOUT (clk_serial),
.LOCK (pll_locked),
.CLKOUTP (),
.CLKOUTD (),
.CLKOUTD3 ()
);
CLKDIV #(
.DIV_MODE ("5")
) clk_pixel_gen (
.HCLKIN (clk_serial),
.RESETN (grst_n),
.CALIB (1'b0),
.CLKOUT (clk_pixel)
);
// SYNC作成
reg [10:0] h_cnt;//0~1499
reg [9:0] v_cnt; //0~799
always @(negedge grst_n or posedge clk_pixel)
if (!grst_n) h_cnt <= 0;
else if (h_cnt != 1499) h_cnt<=h_cnt+1; //14+56+64+1366
else h_cnt<=0;
always @(negedge grst_n or posedge clk_pixel)
if (!grst_n) v_cnt <= 0;
else if (h_cnt==1499) begin
if (v_cnt != 799) v_cnt<=v_cnt+1; //1+3+28+768
else v_cnt<=0;
end
assign hsync=(h_cnt>=14 && h_cnt<14+56)?1'b1 : 1'b0;
assign vsync=(v_cnt>=1 && v_cnt<1+3)?1'b1 : 1'b0;
assign dataen=(h_cnt>=14+56+64)&&(v_cnt>=1+3+28)?1'b1 : 1'b0;
// 画像データ作成
assign data_r=h_cnt[9:2];
assign data_g=v_cnt[7:0];
assign data_b={v_cnt[9:8],6'b11111};
DVI_TX_Top dvi_tx (
.I_rst_n (grst_n),
.I_serial_clk (clk_serial),
.I_rgb_clk (clk_pixel),
.I_rgb_vs (vsync),
.I_rgb_hs (hsync),
.I_rgb_de (dataen),
.I_rgb_r (data_r),
.I_rgb_g (data_g),
.I_rgb_b (data_b),
.O_tmds_clk_p (hdmi_clk_p),
.O_tmds_clk_n (hdmi_clk_n),
.O_tmds_data_p (hdmi_data_p),
.O_tmds_data_n (hdmi_data_n)
);
endmodule
IP core
ソースのほかに、HDMI(DVI)出力用のIP coreの設定が必要です。
メニュバーのTools→IP Core Generatorで作ります。
IPの一覧が表示されますので、Soft IP Coreの下のMultimediaの下のDVI TXをダブルクリックします。
設定の変更が必要な個所は、下記の赤枠の箇所です。
・Using External Clock
シリアルクロックも外部から与えるようにします。この設定値をオフにすると、ピクセルクロックのみで動作し、IP Coreの中にPLLを持ち、5倍クロックを作成してシリアルクロックとします。PLLがもったいないですし、PLLを直列に2つつなぐことになり、特性としてよくないので、今回はIP Coreの外側でクロックを作成して与えます。
・IO Setting
TLVDS(true LVDS)かELVDS(Emulated LVDS)かを選択します。おそらくTLVDSはチップの特定の端子にしかアサインできないようです。
今回はELVDSを使用しないとだめなようです。正直よくわかりません。
・Disable I/O insertion
IOバッファをIP core内部にもつか、外部に持つかの設定のようです。今回は別途IOバッファを使用しませんので、チェックを外します。
OKを押すとプロジェクトに追加するかを聞かれますので、OKします。
ここまで進んだ段階でSynthesizeをし、端子制約設定に進みます。
FloorPlan(I/O Constraints)
動かす
配置配線&書き込みは、Tang Nano 9KでLチカを参考にしてください。
動いた
ちなみに、ビデオキャプチャなどを経由すると表示されない場合があります。
これは、ビデオキャプチャがRGB形式に対応していないものがあるためです。途中にセレクタや分配器などを通さないで試したほうがよいでしょう。
最後に
GOWINは前回のLチカで初めて触ったFPGAですが、日本語の資料も多く、触りやすいイメージです。
お値段は秋月特価で格安ですが、実際のところ正規に搭載チップ1個のみを購入すると結構お高いので、自分でボードを作って乗せたりする際は値段のメリットはあまり受けられないかもしれませんね。アリエクだとチップ単体でも格安で購入できそうですが。
HDMI出力として使うのであれば、1080p/60Hzが出せないのが残念なところではあります。
謝辞
以下のページを参考にさせていただきました。有益な情報を整理していただけたことを感謝申し上げます。
FPGA Sipeed Tang Nano 9Kを使ってHDMI表示(720p) https://spend-carefree.hatenablog.com/entry/2022/08/24/201824
投稿者の人気記事
-
lyricalmagical
さんが
前の月曜日の23:06
に
編集
をしました。
(メッセージ: 初版)
-
lyricalmagical
さんが
前の水曜日の4:43
に
編集
をしました。
(メッセージ: 誤記修正)
ログインしてコメントを投稿する