Привет хабр — в этой статье я собираюсь поделиться своими успехами в освоении ПЛИС 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 МГц, присутствующий на отладочной плате.
Видеопамять и обмен данными с процессором пока реализовывать не будем — вместо этого сами будем генерировать поток битов для контроллера атрибутов.
Итак — задача разбивается на реализацию следующих блоков:
- Синхронизатор — частота 50 мегагерц для нас не очень удобна, поэтому её придется немного изменить
- Контроллер ЭЛТ, который будет выдавать сигналы синхронизации в соответствии с требованиями стандарта VGA
- Генератор картинки — будем использовать для отладки — блок будет рисовать простейшую картинку и выдавать её контроллеру ЭЛТ
Синхронизатор
Начнем с того, что лезем на Английскую Вики и смотрим 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. В перспективе хочется сделать, чтобы контроллер генерировал видеопоток и посылал его видеоадаптеру, а тот выводил на экран. О практическом смысле этого всего говорить конечно не стоит — вещи уже устаревшие и довольно примитивные, всё делалось лишь с целью обучения и закрепления навыков, где то конечно тут набыдлокодил, так что если есть какие то вопросы, советы и замечания — пишите в комментариях
