На выходных завершил проект простого текстового дисплея. Идея проекта действительно простая. Внутри ПЛИС Cyclone III на плате Марсоход2 реализуем текстовый видеоадаптер. Приходящие из последовательного порта символы печатаем в экран. И это почти все.
Проект платы Марсоход2 для среды Altera Quartus II можно взять здесь:
Топ модуль проекта выполнен в графическом виде и вот так он выглядит (можно кликнуть, чтобы увеличить):
Модуль serial принимает байты из последовательного порта на скорости 230400 бит в секунду и записывает их в ФИФО serial_fifo. Модуль sterm читает принятые символы из ФИФО и записывает их в текстовый видеоадаптер txtd. Модуль txtd выдает видеосигнал на выходы ПЛИС, на разъем VGA платы Марсоход2.
ФИФО в этом проекту нужно для двух целей. Во-первых, прием данных тактируется от одной частоты 100Мгц, а обработка данных ведется от другой тактирующей частоты 106,5Мгц – это стандартная частота для видеорежима 1440x900 60Гц. Модули ФИФО очень удобны для разделения клоковых доменов, частей схемы, работающих на разных частотах. Во-вторых, ФИФО нужно, чтобы сгладить неравномерность обработки данных. В моем случае неравномерность чтения данных обусловлена тем, что некоторые символы имеют специальное значение. Еще точнее будет сказать, что один символ – особенный. Это символ «перевода каретки», 0x0D. Все приходящие символы я записываю в самую нижнюю строку экрана. И только символ 0x0D должен произвести прокрутку экрана. Нужно переписать все все строки экрана: первую строку в нулевую, вторую в первую, третью во вторую и так далее. Это довольно долго, в смысле не так быстро, как если просто записать новый символ в экран. Поэтому на время прокрутки экрана чтение из ФИФО приостанавливается. Вот ФИФО позволяет не приостанавливать источник данных.
Модуль txtd – это и есть текстовый видеоадаптер, написан на языке Verilog. Пожалуй это самый сложный модуль в проекте. Он генерирует синхросигналы для монитора. Содержит в себе модуль экранной памяти screen2 и модуль памяти шрифта rom_font. Читает символы из экранной памяти, далее, с помощью встроенного VGA шрифта транслирует символы в точки на экране формирая растровое изображение.
В топ модуле можно еще заметить странный модуль screen2. Тут должен признаться я немного поленился. В модуле текстового дисплея уже есть экранная память. Она выполнена в виде такого же модуля screen2 и представляет из себя двухпортовое ОЗУ. Два порта нужны, чтобы легко и независимо можно было бы писать в экранную память и читать из нее. Причем пишут и читают независимые друг от друга модули: sterm и txtd соответственно. Проблема в том, что sterm то же иногда хочет читать из экранной памяти, когда выполняет прокрутку. А еще одного независимого порта на чтение у ОЗУ нет. Именно поэтому я запараллелил два модуля двухпортового озу. В оба модуля пишется одновременно одно и то же, содержимое обоих блоков памяти одинаково. Теперь сам sterm может читать из своей копии экрана, а txtd читает из своей копии экрана. Получилось типа трехпортовое ОЗУ. На самом деле, конечно, решение несколько странное и не экономное. А с другой стороны зачем мне память экономить в этом проекте если ее и так много? А ведь когда-то о таких решениях можно было только мечтать. Для доступа к памяти использовались свободные «окошки», когда дисплейный адаптер не читает из экрана. Специальные сигналы типа WAIT на шине ISA приостанавливали компьютер, если он обращался к экранной памяти не вовремя... Сейчас все можно сделать гораздо проще.
Рассмотрим как происходит запись символов в экран. Я встроил модуль SignalTap в проект, чтобы посмотреть как ведут себя разные сигналы модуля sterm. Я запустил на компьютере программу терминала Teraterm, открыл последовательный порт принадлежащий плате Марсоход2 и все, что набираю в терминале появляется на экране монитора, подключенного к плате.
Здесь видно, что изначально, когда в ФИФО нет принятых байт и сигнал rdempty в единице, модуль sterm находится в состоянии STATE_WAIT_CHAR. Когда появляется готовый символ в ФИФО, то символ забирается сигналом ACK, машина состояний переходит в STATE_WRITE_CHAR. Однако запись не происходит – нет сигнала wr. Машина состояний определила, что символ «непечатный», 0x0D, и перешла в состояние STATE_SCROLL. Адрес для чтения radr последовательно растет 0x0080, 0x0081, 0x0082. Адрес для записи так же последовательно растет 0x0000, 0x0001, 0x0002 и так далее. Сигнал записи wren видно – производится прокрутка содержимого экрана.
Теперь думаю нужно немного рассказать, что такое текстовый экран.
Текстовый экран – это кажется что-то от первобытных компьютеров. В былые «досовские» времена на экране компьютера отображалось 80x25 символов. Один символ занимал на экране 16 строк, ширина символа была 8 точек.
Сейчас мониторы обычно имеют пропорцию 16 к 9. Например, я реализовал стандартный видеорежим 1440x900. Если пытаться отобразить текстовый режим в этом формате, то по вертикали получим 900 / 16 = 56 целых символьных строк. По горизонтали получается 1440 / 8 = 180 символов в строке.. Это как-то много. Я использовал символы шириной 16 точек (растягиваю каждый символ вширь в два раза). Тогда по горизонтали в строке умещается 90 символов. Ну вот, мой текстовый видеорежим будет отображать 90 символов в строке и всего будет 56 строк. Для хранения такого экрана нужно всего 90*56=5040 байт (если символ – это байт). Так как 5040 – это не очень круглое число, то выбираем ближайшее круглое – 8Кбайт. Обычно к каждому символу в текстовом экране еще добавляется байт - его цвет-атрибут. Значит цветной текстовый экран поместится в 16Кбайт. Такой экран можно реализовать прямо внутри ПЛИС во встроенной статической памяти.
Но вот еще... Нужен знакогенератор, проще говоря, шрифт. Я использую стандартный кирилический VGA шрифт.
Шрифт для текстового дисплея - это битовый образ всех символов. Если символов 256, то для их хранения в памяти в формате 8x16 нужно 256*16=4096 байт. Эта память так же легко поместится прямо в ПЛИС Cyclone III.
Параметры стандартного видеорежима беру из спецификации VESA. Для реализации нужного видеорежима нужно обратить внимание на частоту PixelClock, длину строки, положение и длину строчного синхроимпульса, количество строк и положение кадрового синхроимпульса. Параметров много и нужно точно следовать им, иначе монитор не сможет правильно засинхронизироваться от нашего видеосигнала.
Вот самые главные параметры режима 1440x900, 60Гц:
Pixel Clock = 106.500;
Hor Total Time = 14.157; // (usec) = 242 chars = 1936 Pixels
Hor Addr Time = 10.530; // (usec) = 180 chars = 1440 Pixels
H Front Porch = 0.702; // (usec) = 12 chars= 96 Pixels
Hor Sync Time = 1.112; // (usec) = 19 chars= 152 Pixels
/H Back Porch = 1.814; // (usec) = 31 chars= 248Pixels
Ver Total Time = 13.336; // (msec) = 942 lines
Ver Addr Time = 12.741; // (msec) = 900 lines
V Front Porch = 0.042; // (msec) = 3 lines
Ver Sync Time = 0.085; // (msec) = 6 lines
V Back Porch = 0.467; // (msec) = 33 lines
Алгоритм работы текстового видеоадаптера довольно прост.
Во время развертки строки экрана нужно по очереди считывать символы из экранной памяти. Каждый символ занимает на экране 16 линий в высоту. Значит каждая символьная строка будет считываться 16 раз за один кадр.
Считанный из экранной памяти символ – это индекс в знакогенераторе, в памяти шрифта. Умножаем индекс на высоту одного знака, на 16. Получаем адрес байта одной строки знака. Байт содержит 8 бит – это битовая маска для точек на экране.
Посмотрим, как формируется изображение на экране с помощью инструмента SignalTap.
На этих диаграммах видно, что сейчас на экране отображается линия line_count с номером 18Ah. Это значит, что отображается текстовая строка номер 18h, и сейчас отображается 10-я (0Ah) линия знака. Из этих чисел сперва формируется адрес для чтения из экранной памяти screen2.rdaddress = 18h*80h=C00h (в строке 128 символов из которых видны только 90). По адресу экранной памяти C00h считывается символ 31h. Это число используется для формирования адреса чтения из памяти шрифта rom_font.address = 31Ah. Из памяти шрифта считывается байт, формирующий строку символа scr_char_line. Из этого байта уже формируется видеосигнал, например, rr – красный цвет.
Думаю и так уже утомил техническими подробностями.
Поэтому дальше просто покажу короткое видео. Здесь паказано, как из программы терминала Teraterm передаю в плату Марсоход2 текстовый файл, как он быстро прокручивается на экране, как набираю текст в консоли терминала и он появляется на экране монитора, подключенного к плате Марсоход.
В принципе, дальше я надеюсь развить эту идею. Можно попытаться прямо в плате Марсоход2 реализовать протокол стандартного терминала типа VT100. Тогда получится вполне полезное устройство – вторая консоль для линукс компьютера.
Подробнее...