Pull to refresh

VGA адаптер на ПЛИС Altera Cyclone III

Reading time9 min
Views104K
Привет хабр — в этой статье я собираюсь поделиться своими успехами в освоении ПЛИС Altera Cyclone III. После мигания лампочками и игр со счетчиками — решил сделать что то более серьезное. Сделал я простейший VGA адаптер. Об основных его частях и пойдет речь. Статья больше ориентирована на начинающих, так как для опытных эта задача не составит труда, но для освоения, на мой взгляд — хорошая тренировочная задача. Эксперименты я свои провожу на отладочной плате Altera DE0. Описывать схему я буду на Verilog, Среда — Quartus II v 12.0. Итак — добро пожаловать под кат:

Структурная схема VGA адаптера
Начнем с того, что разберемся — из чего состоит VGA и с чем его едят
Изначально VGA создавался для мониторов с электронно- лучевой трубкой, поэтому управляем мы тут не матрицой пикселей, как в LCD дисплеях, а электронным лучом, который бегает по экрану и мы в нужные моменты должны его усиливать/ослаблять или выключать вообще. Когда я начал работать с VGA — удивился — как мало подробной информации, описывающей этот стандарт я смог найти — что впрочем и понятно — он уже устарел и от него отказываются производители. Надеюсь моя статья кому то поможет в освоении стандарта.
Вырезка из статьи Вики про VGA:
VGA (так же, как и EGA) состоит из следующих основных подсистем (в народе словом «секвенсер» называли набор регистров управления доступом к плоскостям видеопамяти):
  • Графический контроллер (Graphics Controller), посредством которого происходит обмен данными между центральным процессором и видеопамятью. Имеет возможность выполнять битовые операции над передаваемыми данными.
  • Видеопамять (Display Memory), в которой размещаются данные, отображаемые на экране монитора. 256 кБ DRAM разделены на четыре цветовых слоя по 64 кБ.
  • Последовательный преобразователь (Serializer или Sequencer) — преобразует данные из видеопамяти в поток битов, передаваемый контроллеру атрибутов.
  • Контроллер атрибутов (Attribute Controller) — с помощью палитры преобразует входные данные в цветовые значения.
  • Синхронизатор (Sequencer) — управляет временны́ми параметрами видеоадаптера и переключением цветовых слоёв.
  • Контроллер ЭЛТ (CRT Controller) — генерирует сигналы синхронизации для ЭЛТ.

В роли синхронизатора у нас будет выступать генератор на 50 МГц, присутствующий на отладочной плате.
Видеопамять и обмен данными с процессором пока реализовывать не будем — вместо этого сами будем генерировать поток битов для контроллера атрибутов.
Итак — задача разбивается на реализацию следующих блоков:

  1. Синхронизатор — частота 50 мегагерц для нас не очень удобна, поэтому её придется немного изменить
  2. Контроллер ЭЛТ, который будет выдавать сигналы синхронизации в соответствии с требованиями стандарта VGA
  3. Генератор картинки — будем использовать для отладки — блок будет рисовать простейшую картинку и выдавать её контроллеру ЭЛТ

Синхронизатор


Начнем с того, что лезем на Английскую Вики и смотрим Signal timings для VGA, откуда узнаём, что частота синхрогенератора должна быть 25,175 МГц.

Вот нам и первая задача — сделать из 50 МГц — 25.175 МГц. Поможет нам в этом деле PLL или ФАПЧ.
При помощи Mega Wizard'а создаём наш PLL с нужными настройками. Для этого идем Tools/MegaWizard Plug-InManager. Там выбираем тип выходного файла Verilog HDL I/O ALTPLL и настраиваем его — устанавливаем входную частоту и затем её преобразовываем. Нам нужно получить частоту 25,175 МГц, при этом мы можем только умножать и делить на целое число, то есть нам нужно перевести число 0,5035 в дробь. Можно умножить на 5035 и поделить на 10000, но заметим, что можно сократить оба числа на 5. Получаем дробь 1007 / 2000. При этом визард сам приблизит требуемый множитель доступной ему дробью. Включаем полученный модуль в проект подключаем на его вход наш клок с платы, а для выхода создаём wire, который потом куда нибудь заведем, главная цель этого пункта достигнута — получить частоту 25,175 МГц.

Контроллер ЭЛТ


Генераторы синхроимпульсов

Двигаемся дальше. Теперь нам нужно создать модули, которые выдают синхросигналы, удовлетворяющие таймингам горизонтальной и вертикальной синхронизации, то есть вот этому для горизонтальной синхрониции:

То есть мы должны перед выдачей данных подержать линию синхронизации в высоком состоянии время А, потом прижать к земле на время B, потом перевести в высокое состояние, подождать время C и только потом идет поток данных — лучик начинает пробегать строчку за время D и мы за это время должны успеть установить все пиксели с частотой 25,175 мегагерц и так для каждой строки. Вертикальная синхронизация работает почти так же, только вместо пикселей мы передаём строчки и за один период синхроимпульса отрисовываем весь кадр.
Задача определилась — создать блок, который реализует описанную выше последовательность, принимая на вход частоту VGA_CLK, из входов еще добавим, пожалуй, сигнал сброса. Реализация такого блока не проста, а очень проста:
При периоде 1/VGA_CLK = 0,03972194637537239324726911618669 микросекунд нам нужно 24 такта на A, 95 на B, 48 на C, и 640 на D. То же самое и для вертикальной синхронизации, но тут удобнее тактироваться не по тактам частоты в 25.175 мегагерц, а по восходящим фронтам сигнала горизонтальной синхронизации, тогда все задержки можно считать в периодах отрисовки строчки. Кроме того у нас есть область «холостого хода» на экране, реально — по тактам экран чуть больше, а рисовать мы должны только в видимой области, поэтому желательно еще, чтобы модуль нам сообщал текущие координаты лучика, да и если мы собираемся назначать каждому пикселю какой то цвет — без знания текущей позиции луча не обойтись.
Модуль синхронизации
module VGA_SYNC(
						input CLK,
						input SYNC_RST_N,
						output reg H_SYNC_CLK,
						output reg V_SYNC_CLK,
						output wire [10:0]oCurrent_X,
						output wire [10:0]oCurrent_Y,
						output reg oSYNC_COLOR);
parameter A_TIME_H = 24;
parameter B_TIME_H = 95;
parameter C_TIME_H = 48;
parameter D_TIME_H = 640;
parameter TOTAL_TIME_H = A_TIME_H + B_TIME_H + C_TIME_H + D_TIME_H;
parameter BLANK_H = A_TIME_H + B_TIME_H + C_TIME_H;

parameter A_TIME_V = 10;
parameter B_TIME_V = 2;
parameter C_TIME_V = 33;
parameter D_TIME_V = 480;
parameter TOTAL_TIME_V = A_TIME_V + B_TIME_V + C_TIME_V + D_TIME_V;
parameter BLANK_V = A_TIME_V + B_TIME_V + C_TIME_V;

reg 			[10:0]H_Counter;
reg 			[10:0]V_Counter;

assign	oCurrent_X	=	(H_Counter >= BLANK_H)	?	H_Counter-BLANK_H	:	11'h0	;
assign	oCurrent_Y	=	(V_Counter >= BLANK_V)	?	V_Counter-BLANK_V	:	11'h0	;

always@(posedge(CLK) or negedge(SYNC_RST_N))
begin
	if(!SYNC_RST_N)
	begin
		H_Counter	<=	1'b0;
		H_SYNC_CLK	<=	1'b1;
	end
	else
	begin
		if(H_Counter < (TOTAL_TIME_H-1))
		H_Counter	<=	H_Counter + 1'b1;
		else
			begin
			H_Counter	<=	1'b0;
			oSYNC_COLOR <= 1'b0;
			end	
		if(H_Counter == A_TIME_H-1)
		H_SYNC_CLK	<=	1'b0;
		if(H_Counter == A_TIME_H + B_TIME_H-1)
		H_SYNC_CLK	<=	1'b1;
		if(H_Counter == BLANK_H)
		oSYNC_COLOR <= 1'b1;
	end
end

always@(posedge(H_SYNC_CLK) or negedge(SYNC_RST_N))
begin
	if(!SYNC_RST_N)
	begin
		V_Counter	<=	1'b0;
		V_SYNC_CLK	<=	1'b1;
	end
	else
	begin
		if(V_Counter < (TOTAL_TIME_V-1))
		V_Counter	<=	V_Counter + 1'b1;
		else
		V_Counter	<=	1'b0;
		if(V_Counter == A_TIME_V-1)
		V_SYNC_CLK	<=	1'b0;
		if(V_Counter == A_TIME_V + B_TIME_V-1)
		V_SYNC_CLK	<=	1'b1;
	end
end
endmodule


Модуль вывода

Собственно импульсы сгенерированы, координаты точки известны, осталось только вовремя включать нужный цвет, пока лучик пробегает строчку — для этого создадим модуль, которому на вход идут сигналы синхронизации, частота 25,175 МГц, 3 шины цвета и координаты, а выход — 4х битный R-2R ЦАП для каждого цвета, ну и далее — VGA разъем
Модуль Вывода
module VGA_OUT(RESET,
					SYNC_COLOR,
					VGA_CLK,
					oVGA_R,
					oVGA_G,
					oVGA_B,
					iVGA_R,
					iVGA_G,
					iVGA_B,
					Current_X,
					Current_Y);
input wire VGA_CLK;
input wire RESET;
input wire SYNC_COLOR;
input wire [10:0]Current_X;
input wire [10:0]Current_Y;
input wire [3:0]iVGA_R;
input wire [3:0]iVGA_G;
input wire [3:0]iVGA_B;
output reg [3:0]oVGA_R;
output reg [3:0]oVGA_G;
output reg [3:0]oVGA_B;
always@(posedge VGA_CLK or negedge RESET)
begin
	
	if(!RESET)
	begin
		oVGA_R	<=	0;
		oVGA_G	<=	0;
		oVGA_B	<=	0;
	end
	else
	begin
				oVGA_R	<=	((SYNC_COLOR == 1)&&(Current_X > 0)&&(Current_Y > 0)) ? iVGA_R : 0;
				oVGA_G	<=	((SYNC_COLOR == 1)&&(Current_X > 0)&&(Current_Y > 0)) ? iVGA_G : 0;
				oVGA_B	<=	((SYNC_COLOR == 1)&&(Current_X > 0)&&(Current_Y > 0)) ? iVGA_B : 0;
	end
end
endmodule

Для проверки системы — сгенерируем какую нибудь картинку — этот блок описан не мной, взял его из экзамплов алтеровских, ну тут в общем то не с чем разбираться — нам от этого модуля нужна только картинка
Модуль Генерации бистрима
module	VGA_BITSTREAM	(	//	Read Out Side
						oRed,
						oGreen,
						oBlue,
						iVGA_X,
						iVGA_Y,
						iVGA_CLK,
						//	Control Signals
						iRST_n,
						iColor_SW	);
//	Read Out Side
output	reg	[9:0]	oRed;
output	reg	[9:0]	oGreen;
output	reg	[9:0]	oBlue;
input	[9:0]		iVGA_X;
input	[9:0]		iVGA_Y;
input				iVGA_CLK;
//	Control Signals
input				iRST_n;
input				iColor_SW;

always@(posedge iVGA_CLK or negedge iRST_n)
begin
	if(!iRST_n)
	begin
		oRed	<=	0;
		oGreen	<=	0;
		oBlue	<=	0;
	end
	else
	begin
		if(iColor_SW == 1)
		begin
			if (iVGA_Y<120)
			begin
				oRed	<=	(iVGA_X<40)						?			0	:
							(iVGA_X>=40 && iVGA_X<80)		?			1	:
							(iVGA_X>=80 && iVGA_X<120)		?			2	:
							(iVGA_X>=120 && iVGA_X<160)		?			3	:
							(iVGA_X>=160 && iVGA_X<200)		?			4	:
							(iVGA_X>=200 && iVGA_X<240)		?			5	:
							(iVGA_X>=240 && iVGA_X<280)		?			6	:
							(iVGA_X>=280 && iVGA_X<320)		?			7	:
							(iVGA_X>=320 && iVGA_X<360)		?			8	:
							(iVGA_X>=360 && iVGA_X<400)		?			9	:
							(iVGA_X>=400 && iVGA_X<440)		?			10	:
							(iVGA_X>=440 && iVGA_X<480 )	?			11	:
							(iVGA_X>=480 && iVGA_X<520 )	?			12	:
							(iVGA_X>=520 && iVGA_X<560 )	?			13	:
							(iVGA_X>=560 && iVGA_X<600 )	?			14	:
																		15	;
				oGreen	<=	0;	
				oBlue	<=	0;
			end
			else if (iVGA_Y>=120 && iVGA_Y<240)
			begin
				oRed	<=	0;
				oGreen	<=	(iVGA_X<40)						?			15	:
							(iVGA_X>=40 && iVGA_X<80)		?			14	:
							(iVGA_X>=80 && iVGA_X<120)		?			13	:
							(iVGA_X>=120 && iVGA_X<160)		?			12	:
							(iVGA_X>=160 && iVGA_X<200)		?			11	:
							(iVGA_X>=200 && iVGA_X<240)		?			10	:
							(iVGA_X>=240 && iVGA_X<280)		?			9	:
							(iVGA_X>=280 && iVGA_X<320)		?			8	:
							(iVGA_X>=320 && iVGA_X<360)		?			7	:
							(iVGA_X>=360 && iVGA_X<400)		?			6	:
							(iVGA_X>=400 && iVGA_X<440)		?			5	:
							(iVGA_X>=440 && iVGA_X<480 )	?			4	:
							(iVGA_X>=480 && iVGA_X<520 )	?			3	:
							(iVGA_X>=520 && iVGA_X<560 )	?			2	:
							(iVGA_X>=560 && iVGA_X<600 )	?			1	:
																		0	;
				oBlue	<=	0;
			end
			else if (iVGA_Y>=240 && iVGA_Y<360)
			begin
				oRed	<=	0;
				oGreen	<=	0;
				oBlue	<=	(iVGA_X<40)						?			0	:
							(iVGA_X>=40 && iVGA_X<80)		?			1	:
							(iVGA_X>=80 && iVGA_X<120)		?			2	:
							(iVGA_X>=120 && iVGA_X<160)		?			3	:
							(iVGA_X>=160 && iVGA_X<200)		?			4	:
							(iVGA_X>=200 && iVGA_X<240)		?			5	:
							(iVGA_X>=240 && iVGA_X<280)		?			6	:
							(iVGA_X>=280 && iVGA_X<320)		?			7	:
							(iVGA_X>=320 && iVGA_X<360)		?			8	:
							(iVGA_X>=360 && iVGA_X<400)		?			9	:
							(iVGA_X>=400 && iVGA_X<440)		?			10	:
							(iVGA_X>=440 && iVGA_X<480 )	?			11	:
							(iVGA_X>=480 && iVGA_X<520 )	?			12	:
							(iVGA_X>=520 && iVGA_X<560 )	?			13	:
							(iVGA_X>=560 && iVGA_X<600 )	?			14	:
																		15	;
			end
			else
			begin
				oRed	<=	(iVGA_X<40)						?			15	:
							(iVGA_X>=40 && iVGA_X<80)		?			14	:
							(iVGA_X>=80 && iVGA_X<120)		?			13	:
							(iVGA_X>=120 && iVGA_X<160)		?			12	:
							(iVGA_X>=160 && iVGA_X<200)		?			11	:
							(iVGA_X>=200 && iVGA_X<240)		?			10	:
							(iVGA_X>=240 && iVGA_X<280)		?			9	:
							(iVGA_X>=280 && iVGA_X<320)		?			8	:
							(iVGA_X>=320 && iVGA_X<360)		?			7	:
							(iVGA_X>=360 && iVGA_X<400)		?			6	:
							(iVGA_X>=400 && iVGA_X<440)		?			5	:
							(iVGA_X>=440 && iVGA_X<480 )	?			4	:
							(iVGA_X>=480 && iVGA_X<520 )	?			3	:
							(iVGA_X>=520 && iVGA_X<560 )	?			2	:
							(iVGA_X>=560 && iVGA_X<600 )	?			1	:
																		0	;
							
				oGreen	<=	(iVGA_X<40)						?			15	:
							(iVGA_X>=40 && iVGA_X<80)		?			14	:
							(iVGA_X>=80 && iVGA_X<120)		?			13	:
							(iVGA_X>=120 && iVGA_X<160)		?			12	:
							(iVGA_X>=160 && iVGA_X<200)		?			11	:
							(iVGA_X>=200 && iVGA_X<240)		?			10	:
							(iVGA_X>=240 && iVGA_X<280)		?			9	:
							(iVGA_X>=280 && iVGA_X<320)		?			8	:
							(iVGA_X>=320 && iVGA_X<360)		?			7	:
							(iVGA_X>=360 && iVGA_X<400)		?			6	:
							(iVGA_X>=400 && iVGA_X<440)		?			5	:
							(iVGA_X>=440 && iVGA_X<480 )	?			4	:
							(iVGA_X>=480 && iVGA_X<520 )	?			3	:
							(iVGA_X>=520 && iVGA_X<560 )	?			2	:
							(iVGA_X>=560 && iVGA_X<600 )	?			1	:
																		0	;
		
				oBlue	<=	(iVGA_X<40)						?			15	:
							(iVGA_X>=40 && iVGA_X<80)		?			14	:
							(iVGA_X>=80 && iVGA_X<120)		?			13	:
							(iVGA_X>=120 && iVGA_X<160)		?			12	:
							(iVGA_X>=160 && iVGA_X<200)		?			11	:
							(iVGA_X>=200 && iVGA_X<240)		?			10	:
							(iVGA_X>=240 && iVGA_X<280)		?			9	:
							(iVGA_X>=280 && iVGA_X<320)		?			8	:
							(iVGA_X>=320 && iVGA_X<360)		?			7	:
							(iVGA_X>=360 && iVGA_X<400)		?			6	:
							(iVGA_X>=400 && iVGA_X<440)		?			5	:
							(iVGA_X>=440 && iVGA_X<480 )	?			4	:
							(iVGA_X>=480 && iVGA_X<520 )	?			3	:
							(iVGA_X>=520 && iVGA_X<560 )	?			2	:
							(iVGA_X>=560 && iVGA_X<600 )	?			1	:
																		0	;
		
			end
		end
		else
		begin
			oRed	<=	(iVGA_Y<120)					?			3	:
						(iVGA_Y>=120 && iVGA_Y<240)		?			7	:
						(iVGA_Y>=240 && iVGA_Y<360)		?			11	:
																	15	;
			oGreen	<=	(iVGA_X<80)						?			1	:
						(iVGA_X>=80 && iVGA_X<160)		?			3	:
						(iVGA_X>=160 && iVGA_X<240)		?			5	:
						(iVGA_X>=240 && iVGA_X<320)		?			7	:
						(iVGA_X>=320 && iVGA_X<400)		?			9	:
						(iVGA_X>=400 && iVGA_X<480)		?			11	:
						(iVGA_X>=480 && iVGA_X<560)		?			13	:
																	15	;
			oBlue	<=	(iVGA_Y<60)						?			15	:
						(iVGA_Y>=60 && iVGA_Y<120)		?			13	:
						(iVGA_Y>=120 && iVGA_Y<180)		?			11	:
						(iVGA_Y>=180 && iVGA_Y<240)		?			9	:
						(iVGA_Y>=240 && iVGA_Y<300)		?			7	:
						(iVGA_Y>=300 && iVGA_Y<360)		?			5	:
						(iVGA_Y>=360 && iVGA_Y<420)		?			3	:
																	1	;
		end
	end
end

endmodule

Главный модуль
module VGA_MAIN(CLOCK_50,
					KEY,
					LEDG,
					VGA_HS,
					VGA_VS,
					VGA_R,
					VGA_G,
					VGA_B,
					SW
					);

input CLOCK_50;
input [2:0]KEY;
input [9:0]SW;
output 		[9:0]LEDG;
output			VGA_HS;					//	VGA H_SYNC
output			VGA_VS;					//	VGA V_SYNC
output	[3:0]	VGA_R;   				//	VGA Red[3:0]
output	[3:0]	VGA_G;	 				//	VGA Green[3:0]
output	[3:0]	VGA_B;   				//	VGA Blue[3:0]
wire VGA_CLK;
wire H_SYNC_CLK;
wire V_SYNC_CLK;
wire RESET;
wire [10:0]Current_X;
wire [10:0]Current_Y;
wire SYNC_COLOR;
reg	[3:0]	iVGA_R;
reg	[3:0]	iVGA_G;
reg	[3:0]	iVGA_B;
wire	[3:0]	irVGA_R;
wire	[3:0]	irVGA_G;
wire	[3:0]	irVGA_B;
assign irVGA_R[3:0] = iVGA_R[3:0];
assign irVGA_G[3:0] = iVGA_G[3:0];
assign irVGA_B[3:0] = iVGA_B[3:0];
assign RESET = KEY[0];
assign VGA_HS = H_SYNC_CLK;
assign VGA_VS = V_SYNC_CLK;
VGA_PLL		u1
		(	.inclk0(CLOCK_50),
			.c0(VGA_CLK)
		);
VGA_SYNC u2
(	.CLK(VGA_CLK),
	.H_SYNC_CLK(H_SYNC_CLK),
	.V_SYNC_CLK(V_SYNC_CLK),
	.SYNC_RST_N(KEY[0]),
	.oCurrent_X(Current_X),
	.oCurrent_Y(Current_Y),
	.oSYNC_COLOR(SYNC_COLOR));
VGA_OUT u3
(
.oVGA_R(VGA_R[3:0]),
.oVGA_G(VGA_G[3:0]),
.oVGA_B(VGA_B[3:0]),
.iVGA_R(iVGA_R[3:0]),
.iVGA_G(iVGA_G[3:0]),
.iVGA_B(iVGA_B[3:0]),
.VGA_CLK(VGA_CLK),
.Current_X(Current_X),
.Current_Y(Current_Y),
.SYNC_COLOR(SYNC_COLOR),
.RESET(RESET)
);
VGA_BITSTREAM u4(.oRed(irVGA_R),
						.oGreen(irVGA_G),
						.oBlue(irVGA_B),
						.iVGA_X(Current_X),
						.iVGA_Y(Current_Y),
						.iVGA_CLK(VGA_CLK),
						.iRST_n(RESET),
						.iColor_SW(SW[0]));
endmodule


Получилась вот такая вот структурная схема:


Ну и видео работы:


В общем — первый шаг с созданию своей видеокарты сделан — осталось приделать видеопамять и связать это всё с внешним контроллером — например STM32. В перспективе хочется сделать, чтобы контроллер генерировал видеопоток и посылал его видеоадаптеру, а тот выводил на экран. О практическом смысле этого всего говорить конечно не стоит — вещи уже устаревшие и довольно примитивные, всё делалось лишь с целью обучения и закрепления навыков, где то конечно тут набыдлокодил, так что если есть какие то вопросы, советы и замечания — пишите в комментариях
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 45: ↑44 and ↓1+43
Comments20

Articles