NES on FPGA 2009/11/29

VGA

 実機の信号出力はNTSCですが、開発環境にテレビを持ち込むのは面倒で、 画質も劣化します。多くの映像機器で受信可能であることと、 何よりも目の前にディスプレイがあるわけですから、 映像信号はVGAで出力することにしました。 評価ボードにはLVDSポートがついているのでDVIでの出力も可能かもしれませんがこれはおいといて。

 VGA信号で使用されるラインは、Red、Green、Blue、水平同期、垂直同期、グランドがあります。 受信側は水平同期信号及び垂直同期信号をトリガとしてRGB信号をディスプレイに表示します。 このときのドット周波数は約25MHzとなっています。

_
▼ CQSP2E300ボードでの実装

 評価ボードではベースクロックを33MHzとしているため、 このまま出力しては横幅が縮まってしまいます。 そこで横幅を3倍にして出力し、ディスプレイの方で横幅を調整してもらいます。

 PPUの描画はNTSCに合わせて行われるため、 このままVGAへ出力するにはクロック数が足りません。 このためラインベースのダブルバッファリングを使用し、 バッファをダブルスキャン&フリップすることでPPU描画を間に合わせます。 つまり同じラインを2度出力することになるため、 縦幅が2倍になります。

 結果的に、NESの画面領域は横768ドット→縮小されて640ドット、 縦480ドットになりました。
VGAコネクタ回路

 方針が決定したのでFPGA内部で水平同期信号、垂直同期信号を生成し、 カラーはRGB各3ビットの信号をそれぞれ出力しました。 またこのままでは不便なのでVGAコネクタ用ボードを製作しました。 といってもDAC用抵抗とD-subコネクタを乗っけただけですが。 ボードによって発色が違うような気がするのは、 抵抗値や配線などの僅かな違いによるものでしょうか。


_
▼ DE1ボードでの実装

 DE1にははじめからVGA出力コネクタが実装されており、 クロックも50MHzから25MHzを生成できるのでちょうど水平2倍で出力しました。

 垂直方向はやはりダブルバッファリングが必要なためこれも2倍です。 ただし、実質的に上下8ドットは表示されないことが多いため、 NESの画面領域は横512ドット、縦448ドットになりました。

 ということで vga_ctrl.sflp と VGA_test.sflp

%i "vga_ctrl.h"

circuit VGA_test
{
	VGA_ctrl vga;
	output VGA_HS, VGA_VS;
	output VGA_R<4>, VGA_G<4>, VGA_B<4>;
	reg_wr red_out_reg<4>, grn_out_reg<4>, blu_out_reg<4>;

	reg_wr clock25M_div;
	Display dis; // ラインバッファ
	reg_ws dis_timing;

	stage_name clock25M { task do(); }

	par{
		vga.vt_su = 0b0000000001; // 1
		vga.vt_vu = 0b0000011111; // 31
		vga.vt_nu = 0b0000110001; // 49
		vga.vt_nd = 0b0111110001; // 497 (nu + 224*2)
		vga.vt_vd = 0b0111111111; // 511 (vu + 480)
		vga.vt_sd = 0b1000001011; // 524 -1

		vga.ht_su = 0b0001011100; // 92
		vga.ht_vu = 0b0010010111; // 151
		vga.ht_nu = 0b0010111100; // 190 -2
		vga.ht_nd = 0b1010111100; // 702 -2 (nu + 512)
		vga.ht_vd = 0b1011100101; // 741 (vu + 640 -a)
		vga.ht_sd = 0b1100011010; // 795 -1

		if(vga.win_valid){
			red_out_reg := dis.r_out || 0b0;
			grn_out_reg := dis.g_out || 0b0;
			blu_out_reg := dis.b_out || 0b0;
		}
		else{
			// NES画面外の色(ディスプレイ自動幅調整のため)
			red_out_reg := 0b0010;
			grn_out_reg := 0b0010;
			blu_out_reg := 0b0010;
		}

		VGA_HS = vga.hsync;
		VGA_VS = vga.vsync;
		VGA_R = red_out_reg & (4#vga.view_valid);
		VGA_G = grn_out_reg & (4#vga.view_valid);
		VGA_B = blu_out_reg & (4#vga.view_valid);
	}

	instruct vga.dis par{
		if(dis_timing) dis.read();
		dis_timing := ^dis_timing;
	}

	instruct vga.nes_hsync par{
		dis.bank_change();
		generate nes_line.do();
	}

	instruct vga.nes_vsync par{
		pad.key_get();
	}

	stage clock25M {
		par{
			clock25M_div := ^clock25M_div;
			if(clock25M_div) vga.run();
		}
	}
}

Copyright(C) pgate1 All Rights Reserved.