fbpx
20.8 C
София

Използване на FPGA за генериране на VGA видео

Най-четени

Даниел Десподов
Даниел Десподовhttps://www.kaldata.com/
Новинар. Увличам се от съвременни технологии, информационна безопасност, спорт, наука и изкуствен интелект.

Оригиналът е на популярния специалист по хардуер и софтуер Ken Shirriff.

Тук ще опиша как се генерира качествен видеосигнал с помощта на FPGA (програмируема логическа матрица). Като пример съм взел популярната задача FizzBuzz, която често се дава на програмистите при интервю за работа. Генерирането на видео се оказа много по-лесно отколкото съм очаквал. И просто защото ми бе интересно, добавих анимация, разноцветен текст и думи с голям шрифт, които се движат насам-натам по екрана.

FizzBuzz с помощта на FPGA. Моята експериментална платка директно генерира VGA сигнал с анимация на думите Fizz и Buzz

Ако не знаете какво е FizzBuzz, нека да обясня, че това е задача за написване на програма, която показва на екрана или разпечатва на принтер числата от 1 до 100, при което всички кратни на три се заменят с думата Fizz, кратните на пет – с думата Buzz, а кратните на 15 – с FizzBuzz. Тъй като програмата се реализира в рамките на само няколко реда програмен код,  тази задача често се дава при интервютата за кандидатстване за работа, за да се отсеят кандидатите, които съвсем не умеят да програмират. Само че решението с FPGA е много по-сложно.

FPGA (програмируемата логическа матрица) е много интересен чип. Тя може да бъде програмирана по такъв начин, че да бъде реализирана произволна цифрова логика. Може да бъде реализирана изключително сложна схема, без да се налага да се правят физически съединения между отделните логически елементи и тригери.  От този чип можете да направите на практика всичко, което си поискате – от логически анализатор до микропроцесор и видео генератор. За моя проект аз използвах готовата платка Mojo FPGA:

Платката Mojo FPGA. Най-големият чип е Spartan 6 FPGA

Генерирането на VGA сигнала

При FPGA, кривата на обучение е доста стръмна и това не е много лесен процес понеже разработвате схеми, а не пишете програма, която се изпълнява от процесор. Но ако с помощта на FPGA можете да накарате да мигат 5 светодиода, то като цяло сте готови да реализирате и генерирането на VGA видеосигнал. VGA сигналът се оказа много по-опростен и лесен, отколкото очаквах – само три сигнала за пикселите (червен, зелен и син) и два сигнала за хоризонтална и вертикална синхронизация.

Основната идея е да се използват два брояча: един за пикселите по хоризонтала и един за линиите по вертикала. За всяка точка на екрана с тези координати се генерира необходимия цвят на пиксела. Освен това, необходимо е да се генерират и импулсите за хоризонтална и вертикална синхронизация, когато броячите достигнат някои точно определени позиции. Конкретно в този случай аз използвах базовата VGA резолюция от 640х480 пиксела, при която е необходимо да се брои до 800 и до 525. Всяка една хоризонтална линия включва 800 пиксела, от които 640 видими, 16 празни, 96 за хоризонталната синхронизация и още 48 празни пиксела, В същото време вертикалният брояч трябва да брои до 525 – 480 линии на изображението, 10 празни, две линии за вертикалната синхронизация и още 33 празни.

Тази информация ми бе достатъчна, за да напиша кода на модула vga за генериране на VGA сигналите. Кодът е написан на Verilog, който е стандартният език за програмиране на FPGA. Тук няма подробно да се спирам на Verilog и само ще покажа как работи всичко. В показания по-долу код са реализирани броячите x и y. Първият ред показва, че всяко действие се извършва по фронта на всеки тактов сигнал при работна честота на чипа 50 MHz. Следващият ред превключва clk25 на всеки един такт, като по този начин се генерира сигнал с честота 25 MHz. Тук може да стане объркване, понеже <= указва за присвояване, а не за сравнение. Този код увеличава значението на брояча x от 0 до 799 . В края на всеки ред, y се увеличава от 0 до 24. По този начин кодът създава необходимите броячи на пикселите и линиите.

always @(posedge clk) begin
  clk25 <= ~clk25;
  if (clk25 == 1) begin
    if (x < 799) begin
      x <= x + 1;
    end else begin
      x <= 0;
      if (y < 524) begin
    y <= y + 1;
      end else begin
    y <= 0;
      end
    end
  end
end

Въпреки че езикът Verilog прилича на обикновен програмен език, той всъщност работи по съвсем друг начин.  Този код не генерира инструкции, които последователно се изпълняват от процесора, а създава точно определена логическа схема в FPGA чипа. Той създава регистри от тригери за clk25, x и y. За увеличаването на x и y се създават двоични суматори. Операторите if превръщат логическите елементи в компаратори, контролиращи регистрите. Цялата схема работи паралелно и се води от тактовата честота 50 MHz. За разбиране работата на FPGA е необходимо да се излезе от последователното програмно мислене и да се разсъждава на ниво базови логически схеми.

Следният код генерира сигналите за хоризонтална и вертикална синхронизация според значението на броячите  x и y. Освен това, флагът valid указва областта 640х480, където трябва да се формира видео сигналът. Извън пределите на тази област екранът трябва да е празен. Както и преди, тези оператори генерират необходимите логически елементи за проверка на условията и не създават код.

assign hsync = x < (640 + 16) || x >= (640 + 16 + 96);
assign vsync = y < (480 + 10) || y >= (480 + 10 + 2);
assign valid = (x < 640) && (y < 480);

„Полезната част“ на VGA сигнала са червените, зелените и сините пикселни сигнали, които управляват всичко, което се показва на екрана. За да проверя каква схема съм създал, написах няколко реда за активирането на r, g и b в различни области на екрана, като всичко извън пределите на видимостта трябва да бъде блокирано. Въпросителният знак е троичен условен оператор като в Java:

if (valid) begin
  rval = (x < 120 || x > 320) ? 1 : 0;
  gval = (y < 240 || y > 360) ? 1 : 0;
  bval = (x > 500 && (y < 120 || y > 300)) ? 1 : 0;
end else begin
  rval = 0;
  gval = 0;
  bval = 0;
end

Стартирах кода на FPGA платката и бях приятно учуден, че генерирането на VGA стана още от първия път, въпреки че това са просто едни произволни цветни блокове.

Не е много смислено, но всичко работи добре

Извеждане на символи на екрана

Следващата стъпка е показването на текстови символи на екрана. Реализирах модул за генериране на пиксели с размер на матрицата 8х8. Вместо да поддържам целия ASCII код, създадох само символите, необходими за FizzBuzz: от 0 до 9, “B”, “F”, “i”, “u”, “z” и интервал. Получи се доста удобно, понеже всички се събра в рамките на 16 символа – тоест, 4-битово значение.

По този начин модулът получава 4-битов код на символа и 3-битов номер на реда (за 8-те реда на всеки символ) и извежда общо осем пиксела за всеки ред на съответния символ. Фрагментът на кода (пълният код е даден ето тук) за реализацията на символите е просто един голям оператор case за извеждане на съответните битове. На практика този код отива в ROM-а на FPGA във вид на таблица.

case ({char, rownum})
  7'b0000000: pixels = 8'b01111100; //  XXXXX  
  7'b0000001: pixels = 8'b11000110; // XX   XX 
  7'b0000010: pixels = 8'b11001110; // XX  XXX 
  7'b0000011: pixels = 8'b11011110; // XX XXXX 
  7'b0000100: pixels = 8'b11110110; // XXXX XX 
  7'b0000101: pixels = 8'b11100110; // XXX  XX 
  7'b0000110: pixels = 8'b01111100; //  XXXXX  
  7'b0000111: pixels = 8'b00000000; //

  7'b0001000: pixels = 8'b00110000; //   XX    
  7'b0001001: pixels = 8'b01110000; //  XXX    
  7'b0001010: pixels = 8'b00110000; //   XX    
  7'b0001011: pixels = 8'b00110000; //   XX    
  7'b0001100: pixels = 8'b00110000; //   XX    
  7'b0001101: pixels = 8'b00110000; //   XX    
  7'b0001110: pixels = 8'b11111100; // XXXXXX  
...

Промених малко по-горната програма, за да могат да се използват младшите битове на координата x за символа и за пикселния индекс, както и младшите битове на координата y за индекса на редовете. Резултатът е показан по-долу. Текстът в червеното поле е увеличен, за да могат да се видят символите.

Заради допусната от мен грешка всичко е обърнато на обратно

Да де, в моя генератор на символи 7-мият бит е най-отляво, а в неговия пикселен индекс той е отдясно и символите се показват преобърнати. Но това е съвсем лесно за оправяне.

Генериране на редовете за  FizzBuzz

След като оправих показването на символите, трябва да получа правилните символи за показването на FizzBuzz. Ще се спрем само на основните моменти.

Преобразуването на числата от 1 до 100 във вид на символи е тривиално при използване на процесор, но е доста по-сложно, когато имаме работа с цифрова логика., понеже липсва вградена операцията за делене. Деленето на 10 и 100 изисква голям брой логически елементи. Реших да използвам двоично-десетичен брояч (BCD) с отделни 4-битови броячи за всеки бит.

Следващата задача е проверка дали съответното число е кратно на 3 или на 5.  Както и при обикновеното делене, делението с остатък е съвсем лесно за реализиране, когато става дума за процесор, но е доста трудно с цифрова логика.

Вместо да изчислявам конкретни значения, аз създадох още един брояч за остатъците от деленето на 3 и 5. Така например, делението на три с остатък просто отброява 0, 1, 2, 0, 1, 2…

Редът за показването на FizzBuzz съдържа до 8 символа. Модулът fizzbuzz извежда съответните осем 4-битови символа в 32-битовата променлива line. По принцип, нормалният начин за генериране на видео е съхраняването на всичките пиксели в буферната памет, но аз реши да направя това динамически. Операторът if обновява битовете на line, като поставя там “Fizz“, “Buzz“, “FizzBuzz“ или число в зависимост от обстоятелствата:

if (mod3 == 0 && mod5 != 0) begin
  // Fizz
  line[3:0] <= CHAR_F;
  line[7:4] <= CHAR_I;
  line[11:8] <= CHAR_Z;
  line[15:12] <= CHAR_Z;
end else if (mod3 != 0 && mod5 == 0) begin
  // Buzz
  line[3:0] <= CHAR_B;
  line[7:4] <= CHAR_U;
  line[11:8] <= CHAR_Z;
  line[15:12] <= CHAR_Z;
...

На модула FizzBuzz е необходим сигнал за увеличаване на брояча до следващото число и затова промених основния модул vga за индикация на всеки нов ред и го използвах като спусък за прехода към следващото число. Но тестовете показаха странно изображение със сякаш извънземни символи:


Край на първа част. Във втората част ще продължим с правилното изобразяване на символите, с генерирането на цветове за символите и редовете. Ще се спрем върху подробната хардуерна реализация на този интересен проект, показващ основите на работа с FPGA. Допълнително ще се спрем по-подробно на VGA формата.


Коментирайте статията в нашите Форуми. За да научите първи най-важното, харесайте страницата ни във Facebook, и ни последвайте в Telegram и Viber или изтеглете приложението на Kaldata.com за Android, iOS и Huawei!

Абонирай се
Извести ме за
guest

1 Коментар
стари
нови оценка
Отзиви
Всички коментари

Нови ревюта

Moto Razr 40 Ultra – впечатлява от пръв поглед, печели ни с практични решения

В месеците след пандемията, най-накрая пазарът на сгъваеми смартфони, както глобално, така и в България започна да набира скорост, а тази година виждаме все...

Подобни новини