Графический VGA-контроллер на SoC без знаний HDL

4273071acff64abdbc563cca9e4bb357.jpg
Всем привет!
В одной из предыдущих статей мой коллега Des333 реализовал фреймбуфер для LCD, работающего на графическом контроллере ILI9341. Однако, его написание потребовало существенного опыта в разработке RTL-кода.

К тому же, не у каждого под рукой есть embedded LCD-дисплей, зато наверняка есть монитор с VGA-входом.Что же делать, если опыта разработки под FPGA мало, но есть SoC, а сделать что-то интересное хочется?

В этой статье мы расскажем, как разработать графический контроллер, имея на руках плату с SoC (Altera Cyclone V), дисплей с VGA и минимальные знания языков HDL (в нашем случае — Verilog).

Для примера будем использовать наши платки, но всё описанное заработает и на других.
Кому интересно, прошу под кат.

Я планирую рассказывать в следующем порядке:

  1. Сначала немного про архитектуру взаимодействия
  2. Кратко про подключение к VGA
  3. Как получить прошивку, используя только Quartus Qsys
  4. Как объяснить ядру, что есть графический контроллер. Расскажу, что нужно добавить в dtb и собрать драйвера
  5. Как получить терминал и иксы на дисплее

Я буду использовать отладочную плату CB-CV-SOM, работающую вместе с SoDIMM-модулем CV-SE-SOM:
9d4ca09d479f4318a6288234cbe28d55.jpg
К этой отладочной плате у нас есть шилд, на котором помимо VGA есть много интересного (см. metrotek.spb.ru/cbcvsom.html)

Для вывода изображения на дисплей нам нужны фреймбуффер, драйвер и модуль развёртки, который обеспечит связку между процессором и дисплеем, а также обеспечит непрерывное обновление кадров.

В SoC’е к ARM (также называется HPS — Hard Processing System) подключенна DDR3 память (1 GB в нашем случае), в ней и будет находится наш фреймбуффер. А в FPGA будет модуль, который нам нужно будет сделать с помощью Qsys.

Вот схема:

a542d439c753479d8f9d9c2050d791a6.JPG

Работать все будет так:

  • Драйвер настраивает модуль в FPGA: после этого модуль готов получать данные из SDRAM-контроллера и «разворачивать» их на дисплей
  • В /dev/fb0 записываем картинку. Данные попадают во фреймбуфер в DDR
  • Модуль в FPGA непрерывано читает фреймбуффер и обновляет экран

VGA (Video Graphics Array) — это видео интерфейс, использующий аналоговый сигнал для передачи цветовой информации. Формат сигналов и их поведение похожи на тевелизионный сигнал.
Список сигналов:
vga_vs_o — вертикальная синхронизация
vga_hs_o — горизонтальная синхронизация
vga_r_o — данные красной составляющей пикселя
vga_g_o — данные зеленой составляющей пикселя
vga_b_o — данные синий составляющей пикселя

Shield поддерживает 16 бит на цвет, а это значит, что на синий и красный выделяется по 5 бит, а на зеленый 6. ЦАП сделан по схеме R2R.

У мониторов различные разрешения и частота обновления изображения. Эти параметры регулируются в VGA с помощью вертикальной и горизонтальной сихронизаций. У которых есть следующие параметры:

  1. Тактовая частота появления новых пикселей.
  2. Front porch — время гашения синхроимпульса.
  3. Back porch — время нарастания синхроимпульса.
  4. Sync — длительность синхронизации.
  5. Display Area — это момент времени, когда передаётся информация.

Времянки выглядят так:

2ad42379031441c4b21ddaed599e2e10.png

Мы будем использовать режим VGA 800×600 на 60 Hz, cледовательно параметры следующие (параметры для других режимов):
Тактовая частота 40 MHz
Горизонтальная синхронизация:

  • Front porch 40 pixels
  • Back porch 88 pixels
  • Sync 128 pixels
  • Display Area 800 pixels


Вертикальная синхронизация:

  • Front porch 1 lines
  • Back porch 23 lines
  • Sync 4 lines
  • Display Area 600 lines

Чтобы получить прошивку, нам в Qsys потребуются следующие модули:

  • HPS — это наш процессор.
  • Frame Reader — это IP-ядро читает кадры, сохраненные во внешней памяти, и выводит их в виде видеопотока.
  • Clocked Video Output — это IP-ядро, из Avalon-ST делает вывод в VGA подобном формате.
  • Altera PLL — PLL для изменения тактовой частоты: нам нужно получить 40 МГц из 25 МГц, которые есть на плате.

Из процессора выходят AXI интерфейсы H2F и F2H, у IP-ядер Альтеры интерфейсы Avalon-ST и Avalon-MM, поэтому нужен еще модуль межсоединения (Interconnect), который должен конвертировать из одного интерфейса в другой и мультиплексировать потоки данных. Он появится автоматически при генерации файлов.

Подробнее про Frame Reader и Clocked Video Output можно посмотреть тут.
Как собрать прошивку и какие настройки нужны для HPS можно прочитать в этой статье.

Altera PLL


Настройки PLL.

Altera PLL
03a8f9b1a5c14eedb92dc96b0db4d03f.jpg

Frame Reader


Здесь настраиваются:

  • Параметры FIFO на входе модуля
  • Насройки передачи данных
  • Количество активных пикселей


Параметры Bits per pixel per color plane и Number of color planes in parallel связаны с драйвером и объясняются ниже. Обратите внимание, что размерность интерфейса с HPS cовпадает с размерностью Master port width.

Frame Reader
db9ae34efde64046a5b6ded9e63e49cd.png


Clocked Video Output


Здесь:

  • Настройки синхронизаций (vga_vs_o, vga_hs_o), которые описывались выше
  • Способ каким приходят данные (он такой же как и у Frame Reader)


Clocked Video Output
3f50ceee92e94d4fa6f64e6b5b4772f7.jpg

Qsys Connections


И теперь всё соединяем. Настройки для модуля Frame Reader «цепляем» к h2f master, интерфейс для передачи данных f2h slave. Соединяем Clocked Video Output с Frame Reader avalon_streaming_source → din. Все тактируется outclk0.

Qsys Connections
7f1510a0f92d4f81af6bd2d5ec44fae0.jpg

И генерируем файлы, нажав Generate HDL ….

Как выше сказано, на плате 16 бит, а из модуля выходит 32 бита, поэтому нужно внимательно назначить пины в qsf-файле, либо отредактировать выход для себя удобным образом в top файле проекта. Нам нужны старшие биты каждого цвета, они более информативны, чем младшие.

Обратите внимание, что это первое и единственное место, где мы редактируем код. Больше это не потребуется.

logic        vga_v_sync;
logic        vga_h_sync;
 
logic [31:0] vga_data;
 
logic [7:0]  vid_r;
logic [7:0]  vid_g;
logic [7:0]  vid_b;

assign  vga_r_o  = vid_r[7:3];
assign  vga_g_o  = vid_g[7:2];
assign  vga_b_o  = vid_b[7:3];
 
assign  vga_hs_o = vga_h_sync;
assign  vga_vs_o = vga_v_sync;
 
assign { vid_r, vid_g, vid_b } = vga_data;

Нам потребуется драйвер altvipfb.

Вернемся к параметрам Bits per pixel per color plane и Number of color planes in parallel в Frame Reader. В драйвере написано:

if (bits_per_color != 8) {
          dev_err(&fbdev->pdev->dev,
                  "bits-per-color is set to %i.  Curently only 8 is supported.",
                  bits_per_color);
          return -ENODEV;
}

if (!(fbdev->mem_word_width >= 32 && fbdev->mem_word_width % 32 == 0)) {
          dev_err(&fbdev->pdev->dev,
                  "mem-word-width is set to %i.  must be >= 32 and multiple of 32.",
                  fbdev->mem_word_width);
          return -ENODEV;
         }

Число бит на один цвет только 8 и ширина слова должна быть больше или кратна 32. С чем же связано такое ограничение? Смотрим дальше и видим:

/* settings for 32bit pixels */
info->var.red.offset = 16;
info->var.red.length = 8;
info->var.red.msb_right = 0;

info->var.green.offset = 8;
info->var.green.length = 8;
info->var.green.msb_right = 0;
 
info->var.blue.offset = 0;
info->var.blue.length = 8;
info->var.blue.msb_right = 0;

Становится ясно, что драйвер работает в режиме True color, записывая цвет в 32 битное слово (более удобно выравнивать, чем 24), и работает он только в таком режиме.

Чтобы собрать этот драйвер, в конфиге ядра надо внести следующие изменения.

CONFIG_FB_CFB_FILLRECT=m                                       
CONFIG_FB_CFB_COPYAREA=m                                                
CONFIG_FB_CFB_IMAGEBLIT=m
CONFIG_FB_ALTERA_VIP=m

Для того что бы linux узнал, что у нас в FPGA есть фреймбуфер от Альтеры, в dtb надо прописать следующие магические слова:

hps_0_h2f: bridge@0xc0000000 {
    compatible = "altr,bridge-1.0", "simple-bus";
    reg = < 0xc0000000 0x20000000 >;
    #address-cells = < 1 >;
    #size-cells = < 1 >;
    ranges = <0x00000000 0xc0000000 0x4080 >;
    alt_vip_vfr_1: vip2@0x0 {
    compatible = "ALTR,vip-frame-reader-13.0", "ALTR,vip-frame-reader-9.1";
    reg = < 0x4000 0x00000080 >;
    max-width = < 800 >; /* MAX_IMAGE_WIDTH type NUMBER */
    max-height = < 600 >; /* MAX_IMAGE_HEIGHT type NUMBER */
    mem-word-width = < 0x80 >;
    bits-per-color = < 0x8 >;
   };
};

В параметре range — диапазон валидных адресов, с которых драйвер будет читать, а в reg = < 0x4000 0x00000080 > — стартовый адрес и сколько адресов занято alt_vip. mem-word-width это параметр Master port width в Frame Reader.

Подробнее про dtb.

Заходим на прибор и загружаем драйвера:

modprobe altvipfb
modprobe fbcon

Затем проверяем, все ли хорошо с помощью dmesg, и смотрим, есть ли похожая строка:

[   66.424283] altvipfb c0000000.vip2: fb0: altvipfb frame buffer device at 0x2c000000+0x12c000

Ура! Появился fb0:

ls -l /dev/fb0 
crw-rw---T 1 root fb 29, 0 Nov 26 10:13 /dev/fb0

Затем выводим консоль на экран, подключенный к плате:

/sbin/getty 38400 tty1


Ставим icewm и запускаем с помощью startx:

apt-get install icewm icewm-themes
startx

IceWM
f9d58fb4e109441394bd32fe42b5da17.jpg

Итого: мы получили графический контроллер, с минимальными знаниями HDL языков.

© Habrahabr.ru