Jak to działa?
Czy pamiętasz takie programy, jak Norton Commander, Turbo Pascal czy mks_vir? Dzisiaj jest to już cyfrowa archeologia. Te programy, za sprawą pewnych trików, umożliwiały wyświetlenie informacji w atrakcyjnej, jak na tamte czasy, szacie graficznej, przy wykorzystaniu bardzo ubogich zasobów sprzętowych. Działały one w tzw. trybie tekstowym, czyli wyświetlały tylko i wyłącznie litery, cyfry oraz różne kreski, strzałki i inne symbole przygotowane przez producenta karty graficznej.
Wszystkie symbole musiały mieć te same wymiary, a najczęściej stosowało się znaki o wysokości 16 pikseli i szerokości 8 pikseli. Jeżeli monitor miał rozdzielczość 640×480 px, to można było na nim wyświetlić symbole zorganizowane w 80 kolumn i 30 wierszy.
Do dyspozycji jest 256 symboli, ponieważ tyle można zakodować w 8-bitowej zmiennej, czyli w jednym bajcie. Przykład takiego zestawu symboli, zwanego stroną kodową, widzimy na rysunku 2. Istnieje dużo różnych stron kodowych, obejmujących znaki diakrytyczne w wielu różnych językach i innych alfabetach.
We wczesnych systemach komputerowych kolory były 4-bitowe. Trzy bity na składowe poszczególnych kolorów RGB, które pozwalały uzyskać osiem kolorów: czerwony, żółty, zielony, cyjan, niebieski, magentę, biały i czarny. Czwarty bit pozwalał rozjaśnić kolor, co w rezultacie dawało 16 różnych barw.
Każdy symbol ma pierwszy plan (foreground) oraz tło (background). Można im przypisać różne kolory, jednak w obrębie jednego znaku może być wybrana tylko jedna barwa pierwszego planu i jeden kolor tła. Cztery bity kodowały więc kolor pierwszego planu, a kolejne cztery – kolor tła znaku, czyli wystarczył jeden bajt, aby zapisać wszystkie te informacje.
Dochodzimy do wniosku, że za każdy znak wyświetlany na monitorze odpowiedzialne były dwa bajty. Pierwszy ustalał, jaki znak ma być pokazany, a drugi decydował o kolorach. Zatem jeżeli mamy 80 kolumn i 30 wierszy, to razem otrzymujemy 2400 znaków – czyli potrzebujemy 4800 bajtów. To zaskakująco mało jak na obraz o rozdzielczości 640×480 px i 4-bitowej kolorystyce. Gdybyśmy chcieli taki obraz przechowywać w pamięci jako bitmapę, to potrzebowalibyśmy już 153 600 bajtów, czyli 32 razy więcej!
W MachXO2-1200 mamy do dyspozycji siedem bloków EBR o pojemności 1 kB każdy, zatem potrzebujemy pięć takich bloków na pamieć RAM odpowiedzialną za przechowywanie tekstu i koloru – dwa kolejne zostaną nam do innych celów.
Zastanówmy się teraz nad czcionką, ponieważ ona także musi być zapisana w pamięci. Zapewne już domyślasz się, do czego użyjemy pozostałych dwóch bloków EBR. Zobacz rysunek 3, na którym zaprezentowano sposób zapisu jednego znaku w pamięci. Symbole mają rozmiar 16 pikseli w pionie i 8 pikseli w poziomie. Te liczby są nieprzypadkowe i wynikają ze sprytnej optymalizacji – rozdzielczość pozioma to osiem pikseli, dlatego że w jednym bajcie jest właśnie tyle bitów.
Każdy bit tego bajtu odpowiada zatem za jeden piksel. Stan wysoki bitu oznacza, że zostanie on wyświetlony kolorem pierwszego planu, a stan niski spowoduje, że będzie miał taki kolor, jaki wybrany został dla tła.
Cały znak tworzy 16 bajtów i ta liczba również nie jest przypadkowa – wszak to czwarta potęga dwójki (24=16), a 16 kombinacji możemy zapisać na czterech bitach. Każdy symbol ma swój adres w pamięci, a ponieważ każdy z nich wykorzystuje dokładnie 16 bajtów, adres początku znaku (tzn. zerowego bajtu znaku) możemy bardzo łatwo obliczyć wzorem
Adres=16·KodASCII
gdzie KodASCII to numer znaku od 0 do 255. Jednak wcale nie musimy wykonywać żadnego mnożenia! W FPGA operacje mnożenia i dzielanie na liczbach będących potęgami dwójki to de facto... przestawianie bitów. Skoro mamy 256 znaków w pamięci, to potrzebujemy 8 bitów, by zapisać tę liczbę (28=256) – umieszczamy je na bardziej znaczących pozycjach, a numer jednej z szesnastu linii bitmapy czcionki umieszczamy na czterech młodszych bitach. W ten sposób powstaje nam 12-bitowy adres w pamięci.
212=4096 – potrzebujemy więc czterech bloków EBR, aby móc pomieścić całą czcionkę. Jednak jest z tym pewien problem… pozostały nam tylko dwa bloki do dyspozycji. No cóż, albo weźmiemy układ FPGA o większej pamięci, albo obetniemy plik czcionki i zadowolimy się znakami od 0 do 127, czyli wszystkimi literami, cyframi, interpunkcją, znakami kontrolnymi i niektórymi znakami diakrytycznymi. Wybrałem tę drugą opcję, czyli 5 bloków EBR przeznaczyłem na pamięć RAM tekstu i koloru, a 2 bloki EBR będą funkcjonować jako pamięć ROM czcionki. Kody znaków ASCII będą nie 8-bitowe, jak to zwykle bywa, lecz tylko 7-bitowe.
Zastanówmy się teraz, w jaki sposób będziemy dostarczać dane do pamięci RAM tekstu i koloru. W założeniu nasz terminal ma być połączony z komputerem, zatem najprościej będzie wyposażyć go w odbiornik UART (nadajnik nie jest potrzebny, bo wystarczy nam komunikacja jednokierunkowa). Musimy opracować jakiś prosty protokół komunikacji. Zobacz rysunek 5, na którym pokazano dwie ramki transmisji.
Najstarszy bit z przesyłanego bajtu decyduje o znaczeniu pozostałych bitów. Zero oznacza, że pozostałe bity [6:0] to kod ASCII znaku do wyświetlenia, zaś jedynka na tej pozycji – że bity [6:4] to kolor znaku, a pole [2:0] to kolor tła.
Po odebraniu bajtu tekstu przez interfejs UART odpowiadający mu znak ma zostać natychmiast wyświetlony na ekranie, na pozycji aktualnie wskazywanej przez kursor, po czym kursor zostaje przesunięty o jedną pozycję w prawo lub na początek kolejnej linii. Domyślnie terminal będzie wyświetlał białe znaki na czarnym tle. Odebranie bajtu koloru spowoduje zapisanie do rejestru kolorów nowych ustawień i będą one dotyczyły kolejnych odebranych bajtów tekstu – tak długo, aż zostanie przesłany kolejny znak koloru.
Taki sposób komunikacji jest bardzo wygodny ze względu na kompatybilność z różnymi systemami nadrzędnymi. Terminal będzie mógł prezentować znaki pochodzące od dowolnego nadajnika UART, a jeżeli nie obsługuje on kolorów, to terminal wyświetli wszystko białą czcionką na czarnym tle.