FPGAでVGA出力
背景
最近verilogを用いた画像の描画について勉強したので、IntelのSoc FPGA基板、DE10-Nanoを使ってVGA経由でディスプレイに画像を出力しようと思う。
VGA
VGAはR、G、Bのデータをアナログ出力する。
hsyncでスキャンラインを一ピクセル下に移動させ、一画面表示し終えたらvsyncで画面の左上に戻る。
画像サイズの設定
ディスプレイによって対応可能なVGAの画面サイズとリフレッシュレートが異なる。以下の数値が標準。
今回は640x480、60Hzで画像を表示する。
使用するクロックは25MHzとする。
ソース
DE1-Nanoのクロックは50MHz、HPS用に25MHzもFPGAに入力されているが、
このクロックはFPGAのクロックには使えなかった。
そのため50MHzを2分周して25MHzを生成した。
リセット信号は基板上のスイッチを使用。
`include "hvsync_generator.v" /* A simple test pattern using the hvsync_generator module. */ module vga_top(clk, reset, hsync, vsync, rgb); input clk, reset; output hsync, vsync; output [2:0] rgb; wire display_on; wire [9:0] hpos; wire [9:0] vpos; reg clk_divider2; hvsync_generator hvsync_gen( .clk(clk_divider2), .reset(reset), .hsync(hsync), .vsync(vsync), .display_on(display_on), .hpos(hpos), .vpos(vpos) ); wire r = display_on && (((hpos&4'b1111)==0) || ((vpos&4'b1111)==0)); wire g = display_on && vpos[5]; wire b = display_on && hpos[5]; assign rgb = {b,g,r}; always @(posedge clk)begin clk_divider2 <= ~clk_divider2; end endmodule
`ifndef HVSYNC_GENERATOR_H `define HVSYNC_GENERATOR_H /* Video sync generator, used to drive a simulated CRT. To use: - Wire the hsync and vsync signals to top level outputs - Add a 3-bit (or more) "rgb" output to the top level */ module hvsync_generator(clk, reset, hsync, vsync, display_on, hpos, vpos); input clk; input reset; output reg hsync, vsync; output display_on; output reg [9:0] hpos; output reg [9:0] vpos; // declarations for TV-simulator sync parameters // horizontal constants parameter H_DISPLAY = 640; // horizontal display width parameter H_BACK = 48; // horizontal left border (back porch) parameter H_FRONT = 16; // horizontal right border (front porch) parameter H_SYNC = 96; // horizontal sync width // vertical constants parameter V_DISPLAY = 480; // vertical display height parameter V_TOP = 31; // vertical top border parameter V_BOTTOM = 11; // vertical bottom border parameter V_SYNC = 2; // vertical sync # lines // derived constants parameter H_SYNC_START = H_DISPLAY + H_FRONT; parameter H_SYNC_END = H_DISPLAY + H_FRONT + H_SYNC - 1; parameter H_MAX = H_DISPLAY + H_BACK + H_FRONT + H_SYNC - 1; parameter V_SYNC_START = V_DISPLAY + V_BOTTOM; parameter V_SYNC_END = V_DISPLAY + V_BOTTOM + V_SYNC - 1; parameter V_MAX = V_DISPLAY + V_TOP + V_BOTTOM + V_SYNC - 1; wire hmaxxed = (hpos == H_MAX) || !reset; // set when hpos is maximum wire vmaxxed = (vpos == V_MAX) || !reset; // set when vpos is maximum // horizontal position counter always @(posedge clk) begin hsync <= (hpos>=H_SYNC_START && hpos<=H_SYNC_END); if(hmaxxed) hpos <= 0; else hpos <= hpos + 1; end // vertical position counter always @(posedge clk) begin vsync <= (vpos>=V_SYNC_START && vpos<=V_SYNC_END); if(hmaxxed) if (vmaxxed) vpos <= 0; else vpos <= vpos + 1; end // display_on is set when beam is in "safe" visible frame assign display_on = (hpos<H_DISPLAY) && (vpos<V_DISPLAY); endmodule `endif
セットアップ
DE10-NanoにはVGA端子がないのでVGAケーブルをばらしてGPIOに接続できるようにする。
VGAコネクタのソケットのピン番号と各ピンの用途は以下の通り。
RED_RTN、GREEN_RTN、BLUE_RTNは全てGNDに接続する。
SDAとSCLは未接続でよい。
VGAケーブルをバッサリカットし、Red,Green,Blue,Vsync,Hsync,GNDの合計6本を2.54mmピッチの汎用コネクタを半田付けする。
半田付けする際に、R/G/Bのケーブルには直列に300Ωの抵抗を挿入した。
なぜなら、VGAは規格として信号電圧が最大0.7Vであり、ディスプレイ側で75Ωの抵抗がプルダウンされているため、
DE10-Nanoの出力電圧を3.3Vに設定すると、R/G/Bの信号電圧は300Ωと75Ωで分圧されて0.66Vとなるからである。
わかりにくいけど、DE10-Nanoにケーブルを取り付けた写真。
*このVGAケーブルはNCの9ピンに線が接続されていた。何故??
結果
映像がでた。
赤と青のピンアサインを間違えて、伊勢丹みたいになってしまった。まぁいいか。
最初はHsyncとVsyncで勝手に表示画像サイズが決まると勘違いし、画像サイズを適当な値にしていたら、ディスプレイに「Out of Range」と表示されて困った。
その後、画素数とクロックを変更することで事なきを得た。
ここ数週間、色々とFPGAの勉強をしていたけど、こういったアウトプットがあるとモチベーションが高まる。
実践は座学の何倍も価値があるというのを改めて実感。