FPGAボードで遊ぼう！- Basys3でVGA出力 -

# はじめに

　Basys3ボード[^1]はDigilent社が販売しているFPGAボードです．FPGAそのものはリソース量が少ないものの，ペリフェラルが沢山備わっています．そしてなによりXilinx社のチップが乗ったボードの中でもかなり格安なので，ディジタル回路の入門に良さそうです．
　このボードにはVGAコネクタも付いているので，モニタへの動画像の表示も可能です．そこで本記事では，VGA画像を表示するための回路を開発する手順について解説します．そして実際にBasys3を使って画像を表示してみます．

※Basys3でなくても，VGAコネクタが付いたボードなら恐らく同じように画像を表示できるかと思います．是非チャレンジして下さい！（そして出来たらコメント欄で教えて下さい＾＾　励みになります）

# ハードウェアの構成

　Basys3による画像表示のためのハードウェア構成を図1に示します．Basys3に乗っているFPGAチップはVGAコネクタと繋がっています[^2]．このコネクタを介してモニタに映像の情報を送れば，モニタに好きな動画像を表示できます．
　ここで気になるのは，FPGAからどんな信号を出力すれば映像が映るのか，ということです．少し踏み込んで考えると，（１）VGAコネクタからどんな信号を送れば映像が映るのか，（２）FPGAからどんな信号を出力すればVGAコネクタから期待する信号が送れるのか，が分かれば良さそうです．
　[解説1](# 解説1: VGAで映像を表示する)では（１）の『VGAコネクタからどんな信号を送れば映像が映るのか』，[解説2](# 解説2: ディジタル回路でVGA用の信号を出力する)では（２）の『FPGAからどんな信号を出力すればVGAコネクタから期待する信号が送れるのか』について解説します．

![画像を表示するハードウェアの構成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/e2dbae26-2fe3-9adb-c8af-11aea41a1802.png)

図1 画像を表示するハードウェアの構成

# 解説1: VGAで映像を表示する

## VGA

　VGAは15の信号線からなるインターフェースです．VGAコネクタにおける信号の種類を図2に示します．1から3番がそれぞれ赤，緑，青の信号線（まさにRGB），6から8番がそれぞれのグラウンドです．また13番は水平同期信号，14番が垂直同期信号，そして10番がそれらのグラウンドです．ケーブルは２つ端子が付いており，どちらも同じ形状をしていますね．それぞれ同じ番号のピンと結線しているので，どちらを挿しても大丈夫です．
　同期信号に聞き覚えが無い方もいるかもしれません．これはまさに"同期"をとるための信号で，他の信号を読むタイミングを受信側に伝えるために使われます．例えば水平同期信号はモニタに表示する"行"の画素情報を送る度に同期のタイミングを伝えます．対して垂直同期信号は全ての画素情報を送る度に同期のタイミングを伝えます．

![VGAの詳細](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/3b555a1d-1620-3051-d3ac-b280891b352a.png)
図2 VGAの詳細（[1]，[2]より）

## 同期信号のタイミング

　同期信号のタイミングは，表示したい画像の大きさとフレームレート（単位時間当たりに表示する画像の枚数）で決まります．組み合わせは沢山あるのでそれぞれに同期のタイミングがあるわけですが，信号の"意味"はどの組合せでも同じです．図3に同期信号のタイミングを示します．同期のタイミングは（２）なのですが，その前後には（１）フロント・ポーチと（３）バック・ポーチと呼ばれる期間があります．（１）から（３）までの期間は同期に必要な時間で，このとき伝わる色の信号はモニタには表示されません．残りの（４）の期間に色の信号がモニタに表示されます．
　垂直同期も水平同期も図3の方法で同期します．ところで水平同期は行の画素情報が，垂直同期は全ての画素情報が伝わる度に同期が行われると説明しました．そして先ほど同期の期間（（１）から（３））は色の信号はモニタに表示されないとも説明しました．つまり実際に色の信号が表示される期間というのは，垂直同期と水平同期のそれぞれの（４）の期間が重なる間だけになります．

![同期信号のタイミング](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/a8651c92-2ed9-ef68-45f2-afdb113748aa.png)
図3 同期信号のタイミング（サイズ: 640x480, フレームレート: 60Hz，[1]より）

## 動画の表示

　本記事の趣旨とはズレますが，動画を表示したい場合はどうするのでしょうか？その場合はパラパラマンガの要領で画像を表示すると動画になります．**時間方向に映像を切って**，出来た画像を一枚づつVGAで送信すると，ヒトの目には動画に見えるわけです．[^3]

![動画を表示する](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/9ed5d2c7-ab87-86ff-297d-2f8ab573af01.png)
図4 動画を表示する

## FPGAとVGAコネクタ

　FPGAからVGAにはどのように信号を伝えるのでしょうか．Basys3ではFPGAとVGAコネクタの間に抵抗が挟まっています．図5にFPGAとVGAコネクタの接続を示します．注目したいのは色の信号（RED0など）がピンによって異なる抵抗値の抵抗と繋がっている点です．例えばRED3には510Ωの，RED0には4kΩの抵抗が繋がっています．
　高い抵抗値の抵抗を伝わる信号は電圧がより降下します．つまりRED0よりもRED3がハイになったほうが，高い電圧がVGAコネクタに伝わります．するとそれぞれの信号によってVGAに伝わる電圧が異なるので，組み合わることで様々な電圧レベルを出力できます．Basys3はFPGAから色ごとに4ピンづつ信号を出力できるため，2^(4x3)=4096色のバリエーションを使えます．

![FPGAとVGAの接続](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/b052b6ac-94ab-dad7-0a59-9ec8bad9d335.png)
図5 FPGAとVGAの接続（[1]より）

　ここまで分かればあとは必要な信号をFPGAから出力するだけです！

# 解説2: ディジタル回路でVGA用の信号を出力する

　今回はVGA画像（640x480）を60Hzで表示する回路を開発しましょう．今後の拡張も視野に入れて[^4]，表示したい画像はメモリに置き，その画像を読み出して表示する方式の回路を作ります．
　ところでBasys3はリソースがかなり少なめで，VGA画像をオンチップメモリ（Block RAM）に置くことができません．実際はQVGA（320x240）画像ぐらいまでしか置けないので，表示をする回路は画像をVGAサイズに拡大して表示することとします．

## 回路の設計

　VGA画像を表示するのに必要な時間を図6に示します．図の左と下に書かれた値は画素数です．**1画素の表示に要する時間**を単位時間として考えると実装が楽になるので，このような表記をしています．
　1画素の表示にはおよそ40ns(=25.42us/640)，つまりBasys3のFPGAの基本クロック（100MHz）の4クロック分を要します．例えば水平同期のフロント・ポーチは16画素なので，1行当たり640nsを要します．また垂直同期のフロント・ポーチは10画素で，かつ1行が800画素であるため，1行当たり32us，1画像当たり320usを要します．図3の値と比較してみても大体同じですね．

![VGA画像と同期信号](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/bec2ff4a-74c1-c5b1-6b42-73ae74037d58.png)
図6 VGA画像と同期信号（[1]，[3]より）

　回路の構成を図7に示します．クロックを分周回路で周期を1/4にしています．というのも分周によって1画素の表示に要する時間が1クロックとなり[^5]，HDLの記述が簡単になります．ROMには画像のデータが置いてあり，Controlerが図6のタイミングでVGAに出力します．

![画像を表示する回路のブロック図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/97e84b1e-a18b-5c27-bf70-6535380a568a.png)
図7 画像を表示する回路のブロック図

　ROMのサイズは1ワード12ビット，合計で76800ワードです[^6]．図8に画像データのメモリ配置を示します．各ワードが画素に対応し，画像の左上の画素から出力順にメモリへ配置しています．またワードの内容は最上位ビットから4ビットづつ，赤緑青の順で並べています．

![メモリに配置した画像のベタデータ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/2b4e644e-c5f6-2bcd-c567-dc1aa5035f66.png)
図8 メモリに配置した画像のベタデータ

## 回路の実装

　図7のControlerの実装をリスト1に示します．始めのlocalparamは同期に必要なクロック数を定義しています．レジスタ`h_count`と`v_count`はそれぞれ図6の水平方向と垂直方向の座標をカウントしています．このカウントを基にROMのアドレスを算出し（`base_address`と`offset`），さらに同期信号の生成（ソースコード末尾の`hsync`と`vsync`）をしています．

リスト1. BRAMの画像を表示する回路

```verilog:controler.v
`timescale 1ns / 1ps

module controler(
    input         clk,
    input         rst,
    output [16:0] address,
    input  [11:0] data,
    output  [3:0] vgaRed,
    output  [3:0] vgaBlue,
    output  [3:0] vgaGreen,
    output        hsync,
    output        vsync
);    
    localparam [9:0] WIDTH  = 640;
    localparam [9:0] HEIGHT = 480;
    
    localparam [9:0] BEGINNING_OF_HORIZONTAL_SYNC   = 16;
    localparam [9:0] END_OF_HORIZONTAL_SYNC         = BEGINNING_OF_HORIZONTAL_SYNC  + 96;
    localparam [9:0] BEGINNING_OF_DISPLAY_PIXELS_H  = END_OF_HORIZONTAL_SYNC        + 48;
    localparam [9:0] END_OF_ROW                     = BEGINNING_OF_DISPLAY_PIXELS_H + WIDTH;

    localparam [9:0] BEGINNING_OF_VERTICAL_SYNC     = 10;
    localparam [9:0] END_OF_VERTICAL_SYNC           = BEGINNING_OF_VERTICAL_SYNC    + 2;
    localparam [9:0] BEGINNING_OF_DISPLAY_PIXELS_V  = END_OF_VERTICAL_SYNC          + 33;
    localparam [9:0] END_OF_COLUMN                  = BEGINNING_OF_DISPLAY_PIXELS_V + HEIGHT;

    reg [9:0] h_count;
    always@(posedge clk)
    begin
        if (rst)
            h_count <= 0;
        else
            h_count <= (h_count == END_OF_ROW - 1) ? 0 : h_count + 1;
    end

    reg [9:0] v_count;
    always@(posedge clk)
    begin
        if (rst)
            v_count <= 0;
        else if (h_count == END_OF_ROW - 1)
            v_count <= (v_count == END_OF_COLUMN - 1) ? 0 : v_count + 1;
    end
    
    reg [16:0] base_address;
    always@(posedge clk)
    begin
        if (rst)
            base_address <= 0;
        else
            if (v_count < BEGINNING_OF_DISPLAY_PIXELS_V)
                base_address <= 0;
            else if ((h_count == END_OF_ROW - 1) & (v_count[0] == 0))
                base_address <= base_address + WIDTH / 2;
    end

    reg [16:0] offset;
    always@(posedge clk)
    begin
        if (rst)
            offset <= 0;
        else
            if (h_count < BEGINNING_OF_DISPLAY_PIXELS_H)
                offset <= 0;
            else if (h_count[0] == 1)
                offset <= offset + 1;
    end

    assign address = base_address + offset;
    assign {vgaRed, vgaBlue, vgaGreen} = data;    

    assign hsync = ~((BEGINNING_OF_HORIZONTAL_SYNC <= h_count) & (h_count < END_OF_HORIZONTAL_SYNC));
    assign vsync = ~((BEGINNING_OF_VERTICAL_SYNC   <= v_count) & (v_count < END_OF_VERTICAL_SYNC));
endmodule
```

　リスト2は回路合成に必要なXDCファイルです．このファイルと共にVivadoで合成すれば，bitstreamが出来るはずです．

リスト2. XDCファイル

```text:myconstr.xdc
# Clock signal
set_property PACKAGE_PIN W5 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]

# Buttons
set_property PACKAGE_PIN U18 [get_ports rst]
set_property IOSTANDARD LVCMOS33 [get_ports rst]

# VGA Connector
set_property PACKAGE_PIN G19 [get_ports {vgaRed[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaRed[0]}]
set_property PACKAGE_PIN H19 [get_ports {vgaRed[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaRed[1]}]
set_property PACKAGE_PIN J19 [get_ports {vgaRed[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaRed[2]}]
set_property PACKAGE_PIN N19 [get_ports {vgaRed[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaRed[3]}]
set_property PACKAGE_PIN N18 [get_ports {vgaBlue[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaBlue[0]}]
set_property PACKAGE_PIN L18 [get_ports {vgaBlue[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaBlue[1]}]
set_property PACKAGE_PIN K18 [get_ports {vgaBlue[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaBlue[2]}]
set_property PACKAGE_PIN J18 [get_ports {vgaBlue[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaBlue[3]}]
set_property PACKAGE_PIN J17 [get_ports {vgaGreen[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaGreen[0]}]
set_property PACKAGE_PIN H17 [get_ports {vgaGreen[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaGreen[1]}]
set_property PACKAGE_PIN G17 [get_ports {vgaGreen[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaGreen[2]}]
set_property PACKAGE_PIN D17 [get_ports {vgaGreen[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vgaGreen[3]}]
set_property PACKAGE_PIN P19 [get_ports Hsync]
set_property IOSTANDARD LVCMOS33 [get_ports Hsync]
set_property PACKAGE_PIN R19 [get_ports Vsync]
set_property IOSTANDARD LVCMOS33 [get_ports Vsync]

# Configuration options, can be used for all designs
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]
```

　図9にブロックデザインを示します．分周回路はClocking Wizardで作成しました．

![ブロックデザイン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/7abc59bd-c245-9451-2b35-3b4a2bc3390c.png)
図9 ブロックデザイン

## 結果

![Basys3でマンデルブロ集合を表示できた！](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/b5a64089-7a5f-8bde-947d-8d36450e9a52.jpeg)
図10 Basys3でマンデルブロ集合を表示できた！

# おわりに

　Basys3でモニタに画像を表示することができました．VGAには画素情報と同期信号がありました．それぞれの信号を適切なタイミングで出力することで，モニタに映像を表示できることが分かりました．
　次回はこの回路を拡張し，マンデルブロ集合を実際に計算しながら表示する回路を開発していきます．

# リンク

- [controler.v](https://gist.github.com/Kenta11/eac55636205832478118f051398c4167)
- [myconstr.xdc](https://gist.github.com/Kenta11/810f054abb89c0ee8039fe3a39c3f8d8)

# 参考

[1]: Digilent, "Basys 3 Reference Manual, VGA Port," [https://reference.digilentinc.com/reference/programmable-logic/basys-3/reference-manual#vga_port](https://reference.digilentinc.com/reference/programmable-logic/basys-3/reference-manual#vga_port).
[2]: MaxBit Technology, "MaxBit VGA Cables."
[3]: FPGA Blog, "FPGA VGA Graphics in Verilog Part 1," [https://timetoexplore.net/blog/arty-fpga-vga-verilog-01](https://timetoexplore.net/blog/arty-fpga-vga-verilog-01).

# 注釈

[^1]: Xilinx, "Digilent Basys 3 Artix-7 FPGA Board," [https://japan.xilinx.com/products/boards-and-kits/1-54wqge.html](https://japan.xilinx.com/products/boards-and-kits/1-54wqge.html).
    - Digilent社のページ（[https://store.digilentinc.com/](https://store.digilentinc.com/)）や秋月電子（[http://akizukidenshi.com/catalog/](http://akizukidenshi.com/catalog/)）でも購入できるようです
[^2]: Digilent, "Basys 3 Reference Manual, VGA Port," https://reference.digilentinc.com/reference/programmable-logic/basys-3/reference-manual#vga_port.
[^3]: 昔，大学の同期に画像と動画の違いを聞かれたのを思い出したので，説明することにしました．
[^4]: 例えばグラフを描画する回路やPCから画像を受け取る回路を繋げてみるなど...色々な遊び方が考えられますね．
[^5]: 1画素を表示するのに要する時間は変わりません．クロックの周期を変えたので，必要なクロック数が変わるだけです．
[^6]: CPUも無くただ画像データが乗っているだけのROMでも"ワード"と言っても良いのでしょうか？