Генератор цветных полос на FPGA
Сегодня я расскажу, как на отладочной плате DE2-115 от компании terasIC сделать VGA генератор цветных полос.
В качестве предисловия:
Я новичек в мире FPGA, поэтому приведенные коды на Verilog могут быть неоптимальными. Прошу не ругать, как это иногда бывает на habrahabr, что я плохо слушал лекции — я их вообще не слушал, мое образование далеко от электроники, я — самоучка. FPGA достаточно сложная вещь, но бывает, что что-то конкретное гораздо проще реализовать на FPGA, нежели создать программу для микроконтроллера, и наоборот. Генератор цветных полос это как раз тот случай, когда на FPGA это сделать проще. Это очень простой проект, который подходит для начинающих. Конечно, обычно сначала пишут «мигающий светодиод», но на FPGA это как-то до неприличия просто, поэтому я решил написать статью о «мигании монитором».
Если будут просьбы, то могу также написать tutorial по созданию проекта в Quartus II с нуля.
Для начала, давайте посмотрим, какие сигналы требуются, чтобы сформировать картинку.
Конечно, это информация о цвете — три аналоговых сигнала красного ®, зеленого (G) и синего (B) цветов с размахом от 0.7 до 1 В. Т.к. у меня плата DE2-115, а на ней есть video ЦАП, то я буду использовать его. Во-вторых — сигналы вертикальной и горизонтальной синхронизации (vsync и hsync), они цифровые и тактовый сигнал (pixel clock).
Будем использовать разрешение экрана 1680 x 1050 @ 60 Гц, импульс горизонтальной синхронизации отрицательной полярности, вертикальной — положительной. В данном случае pixel clock = 147.14 МГц. Я постарался наглядно изобразить тайминги на рисунке ниже.

Отрисовка строки начинается с импульса горизонтальной синхронизации (hsync) с длительностью 1.25 мкс, далее следует промежуток в 1.96 мкс в течении которого выходы RGB заперты, после него передается цветовая информация — каждый пиксель за один тик и, наконец, пауза длительностью 0.71 мкс в течении которой происходит обратных ход луча на ЭЛТ мониторах.
Импульсы отрисовки кадра генерируются по такому же принципу. Тайминги для разных разрешений экрана вы можете подсмотреть, например, тут.
Как я уже говорил, я буду использовать плату DE2-115. На ней установлен достаточно большой FPGA Cyclone IV от Altera (~115 000 LE) и куча разной периферии. Пользуясь случаем, хочу выразить благодарность компании terasIC и ее предельно корректным и любезным менеджерам, которые согласились предоставить мне данную плату по «студенческой цене», зная, что я уже давным-давно не студент.

Итак, нам нужен генератор pixel clock 147.14 МГц. Тактовый генератор, установленный на плате, работает на частоте 50 МГц, поэтому будем использовать синтезатор частоты PLL. В Cyclone IV их 4 штуки. Генератор развертки и формироватьель полос вынесем в отдельные модули.
Как видно из задачи, для генерации разверки нам потребуется два таймера. Один будет увеличиваться на 1 при каждом тике pixel clock от 0 до суммы всех временных интервалов строки (pulse + back porch + visible area + front porch). А второй будет работать аналогично, но увеличиваться при переднем фронте сигнала hsync. За ноль таймера возьмем начало интервала Front porch.
Далее создадим модуль, отрисовывающий цветные полосы. Для этого нам нужены тактовые испульсы pixel clock. И сигнал blank, для синхронизации.
Давайте посмотрим на последовательность полос:
белый — желтый — голубой — зеленый — пурпурный — красный — синий — черный.
Итого 8. Значит на одну полосу приходится 1680 / 8 = 210 пикселей. Теперь разложим цвета на составляющие
Обратите внимание на порядок следования 0x00 и 0xFF для разных цветов. Ничего не напоминает? Да это переполняющийся 3-х битный счетчик.
Поэтому сигналы RGB очень просто записать так:
где color — это счетчик [2:0]. Еще надо учесть, что во время импульса blank нам надо запирать выходы RGB. Если этого не сделать, ЖК мониторы не смогут автоматически определить границу растра и выровнять его. Вот так:
Теперь нам надо ввести еще один счетчик, который будет считать пиксели (ширину полосы). Он будет увеличиваться на каждом испульсе pixel clock. И сбрасываться на 0 для синхронизации при blank а также при переходе на другую полосу.
Вот конечный код:
Вот и весь собственно генератор.
Теперь соединим все воедино.

Синтезируем (вышло 60 LE) и зальем в плату. Получилось!

Спасибо за внимание!
В качестве предисловия:
Я новичек в мире FPGA, поэтому приведенные коды на Verilog могут быть неоптимальными. Прошу не ругать, как это иногда бывает на habrahabr, что я плохо слушал лекции — я их вообще не слушал, мое образование далеко от электроники, я — самоучка. FPGA достаточно сложная вещь, но бывает, что что-то конкретное гораздо проще реализовать на FPGA, нежели создать программу для микроконтроллера, и наоборот. Генератор цветных полос это как раз тот случай, когда на FPGA это сделать проще. Это очень простой проект, который подходит для начинающих. Конечно, обычно сначала пишут «мигающий светодиод», но на FPGA это как-то до неприличия просто, поэтому я решил написать статью о «мигании монитором».
Если будут просьбы, то могу также написать tutorial по созданию проекта в Quartus II с нуля.
Для начала, давайте посмотрим, какие сигналы требуются, чтобы сформировать картинку.
Конечно, это информация о цвете — три аналоговых сигнала красного ®, зеленого (G) и синего (B) цветов с размахом от 0.7 до 1 В. Т.к. у меня плата DE2-115, а на ней есть video ЦАП, то я буду использовать его. Во-вторых — сигналы вертикальной и горизонтальной синхронизации (vsync и hsync), они цифровые и тактовый сигнал (pixel clock).
Будем использовать разрешение экрана 1680 x 1050 @ 60 Гц, импульс горизонтальной синхронизации отрицательной полярности, вертикальной — положительной. В данном случае pixel clock = 147.14 МГц. Я постарался наглядно изобразить тайминги на рисунке ниже.

Отрисовка строки начинается с импульса горизонтальной синхронизации (hsync) с длительностью 1.25 мкс, далее следует промежуток в 1.96 мкс в течении которого выходы RGB заперты, после него передается цветовая информация — каждый пиксель за один тик и, наконец, пауза длительностью 0.71 мкс в течении которой происходит обратных ход луча на ЭЛТ мониторах.
Импульсы отрисовки кадра генерируются по такому же принципу. Тайминги для разных разрешений экрана вы можете подсмотреть, например, тут.
Как я уже говорил, я буду использовать плату DE2-115. На ней установлен достаточно большой FPGA Cyclone IV от Altera (~115 000 LE) и куча разной периферии. Пользуясь случаем, хочу выразить благодарность компании terasIC и ее предельно корректным и любезным менеджерам, которые согласились предоставить мне данную плату по «студенческой цене», зная, что я уже давным-давно не студент.

Итак, нам нужен генератор pixel clock 147.14 МГц. Тактовый генератор, установленный на плате, работает на частоте 50 МГц, поэтому будем использовать синтезатор частоты PLL. В Cyclone IV их 4 штуки. Генератор развертки и формироватьель полос вынесем в отдельные модули.
Как видно из задачи, для генерации разверки нам потребуется два таймера. Один будет увеличиваться на 1 при каждом тике pixel clock от 0 до суммы всех временных интервалов строки (pulse + back porch + visible area + front porch). А второй будет работать аналогично, но увеличиваться при переднем фронте сигнала hsync. За ноль таймера возьмем начало интервала Front porch.
module vga_sync_generator(pixel_clk,
hsync,
vsync,
blank_N,
pixel_clk_N);
// Параметры для горизонтальной синхронизации
parameter H_ACTIVE_VIDEO = 1680;
parameter H_FRONT_PORCH = 104;
parameter H_SYNC_PULSE = 184;
parameter H_BACK_PORCH = 288;
parameter H_BLANK_PIX = H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH;
parameter H_TOTAL_PIX = H_ACTIVE_VIDEO + H_BLANK_PIX;
// Параметры для вертикальной синхронизации
parameter V_ACTIVE_VIDEO = 1050;
parameter V_FRONT_PORCH = 1;
parameter V_SYNC_PULSE = 3;
parameter V_BACK_PORCH = 33;
parameter V_BLANK_PIX = V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH;
parameter V_TOTAL_PIX = V_ACTIVE_VIDEO + V_BLANK_PIX;
input pixel_clk;
output hsync;
output vsync;
output blank_N;
output pixel_clk_N;
// счетчики
reg [11:0] countV;
reg [11:0] countH;
assign pixel_clk_N = ~pixel_clk; // инвертированный PixelClock
assign blank_N = ~((countV < V_BLANK_PIX) || (countH < H_BLANK_PIX)); // Blank сигнал
// импульс вертикальной синхронизации
assign vsync = (countV >= V_FRONT_PORCH-1) && (countV <= V_FRONT_PORCH + V_SYNC_PULSE-1);
// импульс горизонтальной синхронизации
assign hsync = ~((countH >= H_FRONT_PORCH-1) && (countH <= H_FRONT_PORCH + H_SYNC_PULSE-1));
always @(posedge pixel_clk)
begin
if (countH < H_TOTAL_PIX)
countH <= countH + 1'b1;
else
countH <= 0;
end
always @(posedge hsync)
begin
if (countV < V_TOTAL_PIX)
countV <= countV + 1'b1;
else
countV <= 0;
end
endmoduleДалее создадим модуль, отрисовывающий цветные полосы. Для этого нам нужены тактовые испульсы pixel clock. И сигнал blank, для синхронизации.
Давайте посмотрим на последовательность полос:
белый — желтый — голубой — зеленый — пурпурный — красный — синий — черный.
Итого 8. Значит на одну полосу приходится 1680 / 8 = 210 пикселей. Теперь разложим цвета на составляющие
| красный | зеленый | синий | |
|---|---|---|---|
| бел | 0xFF | 0xFF | 0xFF |
| желт. | 0xFF | 0xFF | 0x00 |
| голуб. | 0x00 | 0xFF | 0xFF |
| зелен. | 0x00 | 0xFF | 0x00 |
| пурпур. | 0xFF | 0x00 | 0xFF |
| красн. | 0xFF | 0x00 | 0x00 |
| синий. | 0x00 | 0x00 | 0xFF |
| черн. | 0x00 | 0x00 | 0x00 |
Поэтому сигналы RGB очень просто записать так:
wire [7:0] blue = color[0] ? 8'hFF : 8'h0;
wire [7:0] red = color[1] ? 8'hFF : 8'h0;
wire [7:0] green = color[2] ? 8'hFF : 8'h0;где color — это счетчик [2:0]. Еще надо учесть, что во время импульса blank нам надо запирать выходы RGB. Если этого не сделать, ЖК мониторы не смогут автоматически определить границу растра и выровнять его. Вот так:
output wire [23:0] rgb = blank ? {blue[7:0], green[7:0], red[7:0]} : 23'b0;Теперь нам надо ввести еще один счетчик, который будет считать пиксели (ширину полосы). Он будет увеличиваться на каждом испульсе pixel clock. И сбрасываться на 0 для синхронизации при blank а также при переходе на другую полосу.
Вот конечный код:
module bars_generator(clk_pixel, blank, rgb);
input clk_pixel;
input blank;
reg [7:0] bar;
reg [2:0] color;
wire [7:0] blue = color[0] ? 8'hFF : 8'h0;
wire [7:0] red = color[1] ? 8'hFF : 8'h0;
wire [7:0] green = color[2] ? 8'hFF : 8'h0;
output wire [23:0] rgb = blank ? {blue[7:0], green[7:0], red[7:0]} : 23'b0;
always @(posedge clk_pixel)
begin
if (!blank)
begin
bar <= 8'b0;
color <= 3'b111;
end
else if (bar < 8'd210)
bar <= bar + 1'b1;
else
begin
bar <= 8'b0;
color <= color - 1'b1;
end
end
endmoduleВот и весь собственно генератор.
Теперь соединим все воедино.

Синтезируем (вышло 60 LE) и зальем в плату. Получилось!

Спасибо за внимание!

0 комментариев