video_main

video_main モジュールは下記のサブモジュールを管理する、ビデオ処理の中枢である。

  • video_timing: 座標の生成, 同期信号の生成, ライン内カウンタ
  • tilemap_renderer: VRAM からデータを取得し、ラインバッファへ処理を送る
  • tilemap_linebuffer: tilemap_renderder から来たデータの保持と、 video_timing から来る画面座標からのデータ出力
  • layer_mixer: tile/sprite_linebuffer から来たデータを重ねてどのレイヤを出力するか指定
  • pallete_ram: CPU からの書き込みと前段処理から読み込み対応
  • 他多数...

layer_mixer と pallete_ram は順番が逆になるかもしない。 tilemap, charcter は前回は video_main の外に置いたけど、今回は中に入れようかと思う(気分の問題)。

今回は NSL で書いていく予定。DRAM コントローラは posedge, negedge を両方使うので NSL では書けないと思う。sprite は NSL で書きたいけど、言語の限界が見えてたので様子見。

ほとんど出来てないが箇条書きの頭から3つはとりあえず作った。

declare video_timing{
	input doubleline;
	output clock_counter[12]; output dot_x[9]; output dot_y[9];
	output video_hsync, video_vsync;
}

declare tilemap_renderer{
	input render_start; output render_end;
	input dot_y[9]; 
	input map0_scroll_x[9]; input map0_scroll_y[8];
	input map1_scroll_x[9]; input map1_scroll_y[8];
	output vram_a[22]; output vram_select; input vram_ready; input vram_data[32];
	output layer;
	output lb_a[6]; output lb_latch; output lb_data[2 + 4 + 32];
}

declare tilemap_linebuffer{
	input dot_x[9]; input dot_y;
	input scroll_x[3];
	input lb_a[6]; input lb_latch; input lb_d[2 + 4 + 32];
	
	output priority[2]; output lb_q[4+4];
}

この3つは C で言うプロトタイプ宣言。モジュールの実装はこのソースには書いていないので、別ソースとリンクする必要有り。ヘッダファイルに別けて #include せよとあるが、コマンドラインの nslcore で #include がこけるので残念な応急処置としてコピペした。

declare video_main{
	output video_r[5], video_g[5], video_b[5];
	output video_hsync, video_vsync;
/*	input cpu_a[5]; input cpu_rd, cpu_wr;
	input cpu_vram_select, output cpu_vram_wait;*/
}

module video_main{
	video_timing timing;
	wire line_render;
	reg tile0_scroll_x[9] = 0;
	reg tile0_scroll_y[8] = 0;
	reg tile1_scroll_x[9] = 0;
	reg tile1_scroll_y[8] = 0;
	line_render = timing.dot_y >= (16 - 1) && timing.dot_y < (256 - 1);

	{
		timing.doubleline = 0;
		video_hsync = timing.video_hsync;
		video_vsync = timing.video_vsync;
	}
	{
		tilemap_renderer tr;
		wire tilemap_lb_a[6]; wire tilemap_lb_latch;
		
		tr.render_start = if(line_render && timing.clock_counter == 4) 0 else 1;

		tr.dot_y = timing.dot_y + 1;
		tr.map0_scroll_x = tile0_scroll_x;
		tr.map0_scroll_y = tile0_scroll_y;
		tr.map1_scroll_x = tile1_scroll_x;
		tr.map1_scroll_y = tile1_scroll_y;
		
		tr.vram_ready = 0;
	}
	{
		tilemap_linebuffer tl0;
		tl0.dot_x = timing.dot_x;
		tl0.dot_y = timing.dot_y[0];
		tl0.lb_a = tr.lb_a;
		tl0.lb_latch = if(tr.layer == 0) tr.lb_latch else 0;
		tl0.lb_d = tr.lb_data;
	}
	{
		tilemap_linebuffer tl1;
		tl1.dot_x = timing.dot_x;
		tl1.dot_y = timing.dot_y[0];
		tl1.lb_a = tr.lb_a;
		tl1.lb_latch = if(tr.layer == 1) tr.lb_latch else 0;
		tl1.lb_d = tr.lb_data;
	}
}

NSL での下位モジュールのつなぎ方の説明がわかりづらかったのだが、要点は下記である。

  • モジュール名と配置名を宣言する
  • 入力線は別の行で、配置名.入力名で接続していく。
  • 出力線は 配置名.出力名 としてどこでも利用可能。

Verilog の場合は宣言と同時に配線をごちゃごちゃいくのだが、 NSL ではCでいう構造体のようにまとめれるので配線やレジスタの宣言が少なくて済む。これは非常に大きい。言語として形を強制するということでカオスな配線名を未然に防げるのは非常によいと思う。(使ったことはないけど python 信者みたいなことだろう)