FPGAでHDMI出力（Full-HD）：その①同期信号/画素データ生成

# 概要
FPGAを使用した、HDMI画像表示回路を作成方法についてまとめました。

# 動作仕様＆使用機材

動作仕様

項目|仕様
---|---|
表示サイズ|Full-HD(1920x1080)
捜査|プログレッシブ
フレームレート|60fps
画像処理クロック|148.5MHz

使用機材

項目|機材
---|---|
FPGA|Spartan6(XC6SLX45-3CSG324)
FPGAボード|[ATLYS](http://store.digilentinc.com/atlys-spartan-6-fpga-trainer-board-limited-time-see-nexys-video/) （DIGILENT社製）


# 回路ブロック
ブロック|Name|概要
---|---|---
同期信号生成部|IMG_SYNC_GEN|同期信号を生成します
画像重畳部|IMG_BRENDER|同期信号に画像データを付加します
エンコーダー|HDMI_TX_ENCODER|8B10B変換を行うエンコーダー
パラシリ変換|HDMI_TX_SERIALIZER|10:1のシリアライザ



![HDMI_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/204268/20f565b6-8671-11be-00ba-ccbe1f9a11ec.jpeg)


* HS・・・水平同期信号
* VS・・・垂直同期信号
* DE・・・データイネーブル（有効画素フラグ）
* R・・・赤色データ（8bit）
* G・・・緑色データ（8bit）
* B・・・青色データ（8bit）

# 同期信号生成部

## 動作仕様
同期信号生成部は、画像同期信号を生成します。
画像同期信号の考え方自体は、VGA（アナログビデオ信号）のときと同様に考えてください。（ここでの説明は省略します）

Full-HDサイズ画像同期信号タイミングを下図に示します。
(FPGA内部の信号なのでHigh優位に統一しています)


![HDMI_2.jpg](https://qiita-image-store.s3.amazonaws.com/0/204268/be015eee-bde7-0200-8496-0ae135d4f4e4.jpeg)

## ソースコード

* トリガのタイミング決定する"水平/垂直設定"はモジュール外からの入力とし、変更可能にする
* "水平/垂直設定"に矛盾した設定が入力された場合は動作保障外とする


```vhdl:IMAGE_SYNC.vhd
	library	ieee;
	use		ieee.std_logic_1164.all;
	use		ieee.std_logic_unsigned.all;
	use		ieee.std_logic_arith.all;
	
	entity IMAGE_SYNC is
		port(
		i_pclk		:in		std_logic						;--ピクセルクロック
		i_ena		:in		std_logic						;--同期信号出力イネーブル
		i_h_cyc		:in		std_logic_vector(11 downto 0)	;--水平設定：周期
		i_h_set		:in		std_logic_vector(11 downto 0)	;--水平設定：同期信号アサート
		i_h_clr		:in		std_logic_vector(11 downto 0)	;--水平設定：同期信号ネゲート
		i_h_act		:in		std_logic_vector(11 downto 0)	;--水平設定：有効期間開始
		i_h_dis		:in		std_logic_vector(11 downto 0)	;--水平設定：有効期間終了
		i_v_cyc		:in		std_logic_vector(11 downto 0)	;--垂直設定：周期
		i_v_set		:in		std_logic_vector(11 downto 0)	;--垂直設定：同期信号アサート
		i_v_clr		:in		std_logic_vector(11 downto 0)	;--垂直設定：同期信号ネゲート
		i_v_act		:in		std_logic_vector(11 downto 0)	;--垂直設定：有効期間開始
		i_v_dis		:in		std_logic_vector(11 downto 0)	;--垂直設定：有効期間終了
		o_vs		:out	std_logic						;--垂直同期信号
		o_hs		:out	std_logic						;--水平同期信号
		o_de		:out	std_logic						);--有効画素フラグ
	end	entity;
	
architecture top of IMAGE_SYNC is

	--=====================================================
	--TYPE
	--=====================================================
	--同期信号セットクリアタイミング
	type typ_VIDEO_TRIGER is record
		ena		:std_logic		;--カウンタイネーブル
		stt		:std_logic		;--動作開始
		fin		:std_logic		;--１周期終了
		inc		:std_logic		;--カウンタインクリメント
		set		:std_logic		;--同期信号アサート
		clr		:std_logic		;--同期信号ネゲート
		act		:std_logic		;--有効期間開始
		dis		:std_logic		;--有効期間終了
	end record;
	
	--=====================================================
	--SIGNAL
	--=====================================================
	signal	ena		:std_logic_vector(0 to 1)		:="00"			;
	signal	ena_rise:std_logic										;
	signal	h_cyc	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	h_set	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	h_clr	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	h_act	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	h_dis	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	v_cyc	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	v_set	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	v_clr	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	v_act	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	v_dis	:std_logic_vector(11 downto 0)	:=(others=>'1')	;
	signal	h_cnt	:std_logic_vector(11 downto 0)	:=(others=>'0')	;
	signal	v_cnt	:std_logic_vector(11 downto 0)	:=(others=>'0')	;
	signal	h		:typ_VIDEO_TRIGER								;
	signal	v		:typ_VIDEO_TRIGER								;
	signal	vs		:std_logic						:='0'			;
	signal	hs		:std_logic						:='0'			;
	signal	ve		:std_logic						:='0'			;
	signal	de		:std_logic						:='0'			;
begin

	--回路イネーブル微分FF
	process(i_pclk)begin
		if(Rising_edge(i_pclk))then
			ena(0)	<=i_ena;
			ena(1)	<=ena(0);
		end if;
	end process;
	
	--回路イネーブル立上り
	ena_rise	<=ena(0) and (not ena(1));
	
	--同期信号タイミング設定値ラッチ
	process(i_pclk)begin
		if(Rising_edge(i_pclk))then
			if(ena_rise='1')then
				h_cyc	<=i_h_cyc;
				h_set	<=i_h_set;
				h_clr	<=i_h_clr;
				h_act	<=i_h_act;
				h_dis	<=i_h_dis;
				v_cyc	<=i_v_cyc;
				v_set	<=i_v_set;
				v_clr	<=i_v_clr;
				v_act	<=i_v_act;
				v_dis	<=i_v_dis;
			end if;
		end if;
	end process;
	
	--カウンタ
	process(i_pclk)begin
		if(Rising_edge(i_pclk))then
			--水平カウンタ
			if(h.stt='1')then
				h_cnt	<=conv_std_logic_vector(1,h_cnt'length);
			elsif(h.ena='1')then
				if(h.fin='1')then
					h_cnt	<=conv_std_logic_vector(1,h_cnt'length);
				elsif(h.inc='1')then
					h_cnt	<=h_cnt+1;
				else
					h_cnt	<=h_cnt;
				end if;
			else
				h_cnt	<=(others=>'0');
			end if;
			--垂直カウンタ
			if(v.stt='1')then
				v_cnt	<=conv_std_logic_vector(1,v_cnt'length);
			elsif(v.ena='1')then
				if(v.fin='1')then
					v_cnt	<=conv_std_logic_vector(1,v_cnt'length);
				elsif(v.inc='1')then
					v_cnt	<=v_cnt+1;
				else
					v_cnt	<=v_cnt;
				end if;
			else
				v_cnt	<=(others=>'0');
			end if;
		end if;
	end process;
	
	--同期信号セットクリアタイミング
	h.ena	<=ena(1)						;
	h.stt	<=ena_rise						;
	h.fin	<='1' when(h_cnt=h_cyc)else '0'	;
	h.inc	<=ena(1)						;
	h.set	<='1' when(h_cnt=h_set)else '0'	;
	h.clr	<='1' when(h_cnt=h_clr)else '0'	;
	h.act	<='1' when(h_cnt=h_act)else '0'	;
	h.dis	<='1' when(h_cnt=h_dis)else '0'	;
	
	v.ena	<=ena(1)						;
	v.stt	<=ena_rise						;
	v.fin	<='1' when(v_cnt=v_cyc)else '0'	;
	v.inc	<=h.fin							;
	v.set	<='1' when(v_cnt=v_set)else '0'	;
	v.clr	<='1' when(v_cnt=v_clr)else '0'	;
	v.act	<='1' when(v_cnt=v_act)else '0'	;
	v.dis	<='1' when(v_cnt=v_dis)else '0'	;
	
	--同期信号
	process(i_pclk)begin
		if(Rising_edge(i_pclk))then
			--垂直同期信号
			if(v.set='1')then
				vs	<='1';
			elsif(v.clr='1')and(v.inc='1')then
				vs	<='0';
			end if;
			--垂直イネーブル
			if(v.act='1')then
				ve	<='1';
			elsif(v.dis='1')and(v.inc='1')then
				ve	<='0';
			end if;
			
			--水平同期信号
			if(h.set='1')then
				hs	<='1';
			elsif(h.clr='1')then
				hs	<='0';
			end if;
			--水平イネーブル(有効画素イネーブル)
			if(h.act='1')then
				de	<=ve;
			elsif(h.dis='1')then
				de	<='0';
			end if;
		end if;
	end process;
	
	--出力ポート
	o_vs	<=vs;
	o_hs	<=hs;
	o_de	<=de;
	
end architecture;
```

# 画像重畳部

画像重畳部はDEを検出して、水平/垂直画素カウントを数えています。
このカウントを利用してRGBの画素値に割り当てて、グラデーションを描いています。
（テスト用に適当に作ったものなので、詳しい説明は省略します。）

```vhdl:IMAGE_GEN.vhd
	library	ieee;
	use		ieee.std_logic_1164.all;
	use		ieee.std_logic_unsigned.all;
	use		ieee.std_logic_arith.all;
	
	entity IMAGE_GEN is
		port(
		i_sync_rst	:in		std_logic						;
		i_pclk		:in		std_logic						;
		i_vs		:in		std_logic						;
		i_hs		:in		std_logic						;
		i_de		:in		std_logic						;
		o_img_vs	:out	std_logic						;
		o_img_hs	:out	std_logic						;
		o_img_de	:out	std_logic						;
		o_img_red	:out	std_logic_vector(7 downto 0)	;
		o_img_grn	:out	std_logic_vector(7 downto 0)	;
		o_img_blu	:out	std_logic_vector(7 downto 0)	);
	end	entity;
	
architecture top of IMAGE_GEN is
	signal	de		:std_logic						:='0'			;
	signal	vs		:std_logic						:='0'			;
	signal	line_end:std_logic										;
	signal	frm_stt	:std_logic										;
	signal	v_cnt	:std_logic_vector(11 downto 0)	:=(others=>'0')	;
	signal	h_cnt	:std_logic_vector(11 downto 0)	:=(others=>'0')	;
	signal	red		:std_logic_vector(7 downto 0)	:=(others=>'0')	;
	signal	grn		:std_logic_vector(7 downto 0)	:=(others=>'0')	;
	signal	blu		:std_logic_vector(7 downto 0)	:=(others=>'0')	;
	
	signal	v_ptr	:std_logic_vector(11 downto 0)	:=(others=>'0')	;
	signal	h_ptr	:std_logic_vector(11 downto 0)	:=(others=>'0')	;
	signal	v_dir	:std_logic						:='0'			;
	signal	h_dir	:std_logic						:='0'			;
	--*********************************************
	begin
	--*********************************************
	
	--ラインの終わり
	line_end	<=de and (not i_de);
	--フレームの始まり
	frm_stt		<=(not vs) and i_vs;
	-----------------------------------
	--画素カウンタ
	-----------------------------------
	process(i_pclk)begin
		if(rising_edge(i_pclk))then
			--
			de	<=i_de;
			vs	<=i_vs;
			--水平カウンタ
			if(i_de='1')then
				h_cnt	<=h_cnt +1;
			else
				h_cnt	<=(others=>'0');
			end if;
			
			--垂直カウンタ
			if(i_vs='1')then
				v_cnt	<=(others=>'0');
			else
				if(line_end='1')then
					v_cnt	<=v_cnt +1;
				else
					v_cnt	<=v_cnt;
				end if;
			end if;
		end if;
	end process;
	
	-----------------------------------
	--十字ポインタ
	-----------------------------------
	process(i_pclk)begin
		if(rising_edge(i_pclk))then
			if(frm_stt='1')then
				if(v_dir='0')then
					v_ptr	<=v_ptr +1;
				else
					v_ptr	<=v_ptr -1;
				end if;
				
				if(h_dir='0')then
					h_ptr	<=h_ptr +1;
				else
					h_ptr	<=h_ptr -1;
				end if;
				
				if(v_ptr=conv_std_logic_vector(1079,12))then
					v_dir	<='1';
				elsif(v_ptr=conv_std_logic_vector(0,12))then
					v_dir	<='0';
				end if;
				
				if(h_ptr=conv_std_logic_vector(1919,12))then
					h_dir	<='1';
				elsif(h_ptr=conv_std_logic_vector(0,12))then
					h_dir	<='0';
				end if;
			end if;
		end if;
	end process;
	
	-----------------------------------
	--ダミー画像生成
	-----------------------------------
	process(i_pclk)begin
		if(rising_edge(i_pclk))then
			if(i_de='1')then
				if(h_cnt=h_ptr)or(v_cnt=v_ptr)then
					red	<=x"00";
					grn	<=x"00";
					blu	<=x"00";
				else
					case(h_cnt(9 downto 8))is
						when "00"=>
							red	<=h_cnt(7 downto 0);
							grn	<=v_cnt(7 downto 0);
							blu	<=x"00";
						when "01"=>
							red	<=h_cnt(7 downto 0);
							grn	<=x"00";
							blu	<=v_cnt(7 downto 0);
						when "10"=>
							red	<=x"00";
							grn	<=v_cnt(7 downto 0);
							blu	<=h_cnt(7 downto 0);
						when others=>
							red	<=h_cnt(7 downto 0);
							grn	<=v_cnt(7 downto 0);
							blu	<=h_cnt(7 downto 0);
					end case;
				end if;
			else
				red	<=x"00";
				grn	<=x"00";
				blu	<=x"00";
			end if;
		end if;
	end process;
	

	o_img_red	<=red;
	o_img_grn	<=grn;
	o_img_blu	<=blu;

	process(i_pclk,i_sync_rst)begin
		if(i_sync_rst='1')then
			o_img_vs	<='0';
			o_img_hs	<='0';
			o_img_de	<='0';
		elsif(rising_edge(i_pclk))then
			o_img_vs	<=i_vs;
			o_img_hs	<=i_hs;
			o_img_de	<=i_de;
		end if;
	end process;
	
end architecture;
```

[その②](https://qiita.com/cyebu1103/items/3de26a84cc57e6c48a66)へつづく