FPGA(Pynq Z1)を用いたHDMI動画出力(verilogHDL)

# はじめに
今回は、友人の研究を手伝うついでに色々コードを書いたので備忘録がてら記録してこうと思います。ソースコード自体は[GitHub](https://github.com/RotaryKer/HDMI_OUT)にあげていますので自由に扱ってください。ちなみに、ソースコードに間違いがあるかもしれないので、この記事を見て、間違いに気づかれた方が指摘してくださることを願っています。

# 目的
友達の手伝いということもあり、Pynq-Z1を使ってHDMIを出力する。
よって準備物は、Pynq-Z1とHDMIケーブルくらいです。あと、実機にbitstreamを載せるためのVivadoくらいですかね。
あとは、digilent社が提供してくださっている[IPコア](https://github.com/Digilent/vivado-library,"IPコア")の中にあるrgb2dviというもの。
これだけで一応HDMI出力はできます。

#参考サイト
今回は実装に際して、この[サイト](https://timetoexplore.net/blog/video-timings-vga-720p-1080p,"サイト")を参考にしました。このサイトでは、制御タイミングがわかりやすく説明してあるので、初めての僕でもわかりやすかった。どこかに日本語に訳されたサイトもあったと思うが、見つけるのがめんどくさかったため、英語で我慢してください。

#実装
まずは、Topモジュールから作成する今回はHDMIを使って映像だけの出力になるので、
TMDSデータと、クロックを作動で送り出すインターフェイスとなる。
入力は、Pynqで生成される125MHzのクロックのみで大丈夫だろう。

```verilog
module HDMI_Top(
  input wire            sysclk,
  input wire            rst,
  output wire           hdmi_tx_clk_n,
  output wire           hdmi_tx_clk_p,
  output wire   [2:0]   hdmi_tx_d_n,
  output wire   [2:0]   hdmi_tx_d_p
);
```

一応Topモジュールはこんな感じでいいと思います。(Pynq)制約ファイルはこれに合わせて作ってください。Pynq用の制約ファイルもgithubにおいています。

次に出力データのタイミングを作るモジュールを作成します。これは、パラメータですぐに変更できるように実装します。

```verilog
  //Horizontal Timing
  parameter H_ACTIVE_PIXEL  =10'd  640;
  parameter H_FRONT_PORCH   =10'd   16;
  parameter H_SYNC_WIDTH    =10'd   96;
  parameter H_BACK_PORCH    =10'd   48;
  parameter H_TOTAL         =10'd  800;
  parameter H_WIDTH         =10'd   11;

  //Vertical Timing
  parameter V_ACTIVE_LINE   =10'd  480;
  parameter V_FRONT_PORCH   =10'd   10;
  parameter V_SYNC_WIDTH    =10'd    2;
  parameter V_BACK_PORCH    =10'd   33;
  parameter V_TOTAL         =10'd  525;
  parameter V_WIDTH         =10'd   10;
```
bit幅は適宜変更してください。今回はめんどくさかったので固定にしています。
参考サイトを確認すると、パラメータが何の値を表しているのかわかると思います。

タイミング制御モジュールのインターフェースは以下のようになります。

```verilog
module HDMI_Timing(
  input wire      clk,
  input wire      rst,
  output wire     hsync,  //水平同期信号
  output wire     vsync,  //垂直同期信号
  output wire     de      //データイネーブル信号
);
```

タイミング制御はモジュール内でカウンタを用いることで実装しています。

```verilog
  reg  [H_WIDTH-1:0]  h_cnt;
  reg  [V_WIDTH-1:0]  v_cnt;
```

output信号線に流すデータは以下のようになります。

```verilog
  assign hsync  = !(h_cnt < H_ACTIVE_PIXEL+H_FRONT_PORCH+H_SYNC_WIDTH
                  && h_cnt > H_ACTIVE_PIXEL+H_FRONT_PORCH-1);

  assign vsync  = !(v_cnt < V_ACTIVE_LINE+V_FRONT_PORCH+V_SYNC_WIDTH
                 && v_cnt > V_ACTIVE_LINE+V_FRONT_PORCH-1);

  assign de     = h_cnt < H_ACTIVE_PIXEL && v_cnt < V_ACTIVE_LINE;
```
ここは参考にした[サイト](https://timetoexplore.net/blog/video-timings-vga-720p-1080p)を確認して作りました。

カウンタのはgithubに落ちているコード見てもらえばすぐにわかると思います。ので、めんどくさいのでここに書くのはやめときます。

次からはvivado内での処理に移ります。
先ほど作成した２つのモジュールと制約ファイルを用いて新規プロジェクトを作成します。
rgb2dviコアは内部でMMCMやPllを用いて、シリアルクロックを生成してくれますが、今回の友人の要望の640x480のサイズを出力するのに必要な、25MHzがデフォルトでは出力されない設計でした。IP内部の設定を変更して、25MHzをレンジに入れてあげることもできますが、（私個人では設定を変更した）簡単に作るために、シリアルクロックと、ピクセルクロックを外部モジュールで生成することがベストではないでしょうか。資源は食いますが、まぁ簡単なので。。。

外部クロックモジュールは、vivadoのIPカタログからClocking_Wizardを使います。
primitiveはMMCMを選択し、Primary clockはPynq-Z1の125MHZを入力してあげます。
OutputClockの設定では、ピクセルクロックの値と、シリアルクロックの値を入力してあげてください。
シリアルクロックはピクセルクロックの5倍の値を入力します。
そして、トップモジュールにインスタンス化してあげれば外部クロックモジュールの組み込みは終了です。
今回はlockedを内部モジュールのリセットに用いています。

```verilog
clk_wiz_0 instance_name
  (
   // Clock out ports
   .clk_out1(pixelclk),     // output clk_out1
   .clk_out2(serialclk),    // output clk_out2
   // Status and control signals
   // .resetn(rst),        // input resetn
   .locked(sysrst),        // output locked
   // Clock in ports
   .clk_in1(sysclk)
  );
```

最後にrgb2dviモジュールのインスタンス化をする必要があります。これは差動信号に変換されたクロックとデータが出力されます。

```verilog
rgb2dvi_0 #(
    .KGenerateSerialClk(0).    //外部でクロック信号を生成する場合
    //.KGenerateSerialClk(1)  //内部でクロック信号を生成する場合
)rgb2dvi_1(
    .TMDS_Clk_p (hdmi_tx_clk_p),
    .TMDS_Clk_n (hdmi_tx_clk_n),
    .TMDS_Data_p(hdmi_tx_d_p),
    .TMDS_Data_n(hdmi_tx_d_n),
    .aRst(!sysrst),
    .vid_pData(24'hff0000),//ここに表示したいRGB値データを入力
    .vid_pVDE(de),
    .vid_pHSync(hsync),
    .vid_pVSync(vsync),
    .PixelClk(pixelclk),   //外部クロックモジュールで生成したピクセルクロック
    .SerialClk(serialclk)  //外部クロックモジュールで生成したシリアルクロック
  );
```

一応これで誰でもできるはずです。最後はvivadoのぽちぽちゲーなので終わりです。
あくまで個人の備忘録としてみてください。

#まとめ
割と簡単にできた。
あんまり長いソースコードを書くのが嫌いなので、パラメータで使う解像度で変更するようにしました。基本的にはトップモジュールと、クロックモジュールを変更してあげるだけで対応はできるかと思います。
今回感じたのはdigilentのIPはRTLでの提供なのでとても親切ってことですかね。使い方はVivadoで
ブロックモジュールにするのもあり。要望があれば、追記します。
#最後に
今回は、いくつかのモニタサイズをPynqを用いて表示させてみたが、モニターによっては640x480を表示できなかったりとバグなのか、モニタの仕様なのかよくわからないが表示できなかったことがあった。モニタの仕様書を探せなかったので、今後はなぜ表示できなかったのかの解決をしつつ、他のプロジェクトを進めることをしようと思う。