Генератор цветных полос на 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 МГц. Я постарался наглядно изобразить тайминги на рисунке ниже.

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

FPGA DE2-115
Итак, нам нужен генератор 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 пикселей. Теперь разложим цвета на составляющие


 красный зеленыйсиний
бел0xFF0xFF0xFF
желт.0xFF0xFF0x00
голуб.0x000xFF0xFF
зелен.0x000xFF0x00
пурпур.0xFF0x000xFF
красн.0xFF0x000x00
синий.0x000x000xFF
черн.0x000x000x00
Обратите внимание на порядок следования 0x00 и 0xFF для разных цветов. Ничего не напоминает? Да это переполняющийся 3-х битный счетчик.
Поэтому сигналы 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

Вот и весь собственно генератор.

Теперь соединим все воедино.

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

FPGA DE2-115
Спасибо за внимание!

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

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.