Kontroler VGA do systemów mikroprocesorowych

Projekty użytkowników forum zarówno sprzętowe, jak i związane z programowaniem w dowolnym języku.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Kontroler VGA do systemów mikroprocesorowych

Postautor: gaweł » czwartek 25 kwie 2019, 21:36

vga01_00.jpg

Trochę historii


Możliwość wyświetlania tekstów na typowym monitorze VGA pochodzących z systemów mikroprocesorowych, w których istnieje możliwość „posiadania” kawałka zewnętrznej pamięci RAM (ot choćby takich jak Z80, M6800, I8085 czy wręcz I8051, niektóre modele AVR lub ATMEGA) jest bardzo nęcąca. Bo któżby nie chciał podłączyć do takiego systemu takiego monitora. Ja w swojej historii zabaw z prockami mam już takie doświadczenie. W czasach, gdy pasjami oddawałem się zabawom z Z80, zbudowałem sobie coś na ten kształt. Wtedy był to dosyć prosty kontroler, mający 1kB pamięci RAM wpiętej w przestrzeń adresową procka, który generował obraz na zwykły telewizor (generował zespolony sygnał wizyjny).
Na ekranie było wyświetlanych 32 wiersze po 32 znaki w wierszu, gdyż zależało mi na jak największej liczbie wierszy. Konstrukcja, pomimo sporych wad, jakoś działała. Cóż, z niektórymi problemami to człowiek dopiero się stykał i wcześniej nie był ich świadomy, jednak każde doświadczenie jest cenne (jak się z jego wyciąga wnioski). Z racji dużej prostoty w rozwiązaniu podstawową wadą było to, że obraz „śnieżył”. Spowodowane jest to tym, że...
Zanim wyjaśnię muszę przedstawić koncepcję rozwiązania. Było to:
vga01_01.png
Układ sterujący to była banda przerzutników, która generowała adres do pamięci RAM (wynikający z pozycji na ekranie, w tym część adresu do generatora znaków), impulsy synchronizacji, stroby do wszystkich rejestrów. W tym znajdował się arbitraż dostępu do pamięci, gdyż jak widać, istnieje możliwość konfliktu dostępu. Układ generowania sygnału wizyjnego musi ciągle być napędzany i czasami procek ma ochotę coś zapisać do pamięci. Wtedy uznałem, że pierwszeństwo będzie należeć do procka a generator nie dostanie danych. Właściwie, to nie ma sytuacji, że nie dostanie, układ wizyjny dostał coś (już nie pamiętam, czy to były same jedynki, czy zera). No i to powodowało wyświetlenie krótkiej kreski poziomej (na szerokość jednego znaku). Jak procek chciał przewinąć ekran, to na ekranie był koszmar. Później dorabiałem jakieś patenty ze sterowaniem typu WAITSTATE czy wręcz HOLD. Tam było jeszcze coś takiego (już nie ująłem tego na rysunku), że najstarsze dwa bity (bo generator zawierał tylko wielkie litery – kod znaku był 6-bitowy) atrybutem, to znaczy znak miał się wyświetlić normalnie lub w negatywie oraz był mrugający (na przemian był wyświetlany znak lub spacja). Wtedy „poznałem” istnienie propagacji sygnału przez bramki. Logika kombinacyjna miała różne długości propagacji (różną liczbę funktorów w torze) i jak były wyświetlane kolejne znaki z różnymi atrybutami, to troszkę atrybuty danego znaku wjeżdżały na następny.
Dzisiaj, mając do dyspozycji bardziej nowoczesne układy można pokusić się o redesign. Widząc ile monitorów zaczyna walać się po kontach, to żal bierze, jak one stoją i „rdzewieją”. Kilka razy rozmyślałem nad jakimś sensownym rozwiązaniem. Tu koncepcja zaczęła zmierzać w stronę pamięci RAM z podwójnym dostępem. Robiąc kolejny jakiś porządek w pracowni w łapy wlazł mi zestaw z BTC z układem SPARTAN 3, no i w mózgu coś się przestawiło (no chyba to nie jest przypadek). Przecież FPGA SPARTAN ma coś co się nazywa „Block RAM” i jest tego całkiem sporo. Tutaj potrzeba 4 kilobajty i można uzyskać cechy jakie miał DOS'owy ekran w trybie tekstowym. Jak by tego było mało, to zestaw ten (ZL9PLD + ZL10PLD z KAMAMI) zawiera już na pokładzie gniazdu jak w kartach VGA z opornikami dającymi możliwość prostej (jednak wystarczającej) konwersji z cyfry na analog.
No to trzeba zawalczyć o nią. Poczyniłem pierwszy eksperyment i utworzyłem komponent w VHDL'u modelujący dwuportową pamięć RAM o wymaganej wielkości. Pierwsze eksperymenty są bardzo obiecujące.

Kod: Zaznacz cały

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity dualport_ram is port ( Clock        : in std_logic ;
                              WriteStrobe  : in std_logic ;
                              WriteAddress : in integer range 0 to 4095 ;
                              DataIn       : in std_logic_vector ( 7 downto 0 ) ;
                              ReadStrobe   : in std_logic ;
                              ReadAddress  : in integer range 0 to 4095 ;
                              DataOut      : out std_logic_vector ( 7 downto 0 ) ) ;
end dualport_ram ;

architecture Behavioral of dualport_ram is

  type Memory is array ( 0 to 4095 ) of std_logic_vector ( 7 downto 0 ) ;

  signal Memory_Array : Memory ;
  signal WrAdrReg : integer range 0 to 4095 ;
  signal RdAdrReg : integer range 0 to 4095 ;
  signal DInReg : std_logic_vector ( 7 downto 0 ) ;
  signal DOutReg : std_logic_vector ( 7 downto 0 ) ;
  signal WrFlag  : std_logic ;
  signal RdFlag : std_logic ;

begin
  process ( Clock )
  begin
    if Clock'event and Clock = '1' then
      WrAdrReg <= WriteAddress ;
      RdAdrReg <= ReadAddress ;
      DInReg <= DataIn ;
      WrFlag <= WriteStrobe ;
      RdFlag <= ReadStrobe ;
    end if ;
  end process ;

  process ( WrFlag )
  begin
    if WrFlag'event and WrFlag = '0' then
      Memory_Array ( WrAdrReg ) <= DInReg ;
    end if ;
  end process ;

  process ( RdFlag )
  begin
    if RdFlag'event and RdFlag = '0' then
      DOutReg <= Memory_Array ( RdAdrReg ) ;
    end if ;
  end process ;

  DataOut <= DOutReg ;

end Behavioral;
Synteza komponentu raportuje, że:

Kod: Zaznacz cały

INFO:Xst:2691 - Unit <dualport_ram> : The RAM <Mram_Memory_Array> will be implemented as a BLOCK RAM, absorbing the following register(s): <DOutReg>.
    -----------------------------------------------------------------------
    | ram_type           | Block                               |          |
    -----------------------------------------------------------------------
    | Port A                                                              |
    |     aspect ratio   | 4096-word x 8-bit                   |          |
    |     mode           | write-first                         |          |
    |     clkA           | connected to signal <WrFlag>        | fall     |
    |     weA            | connected to internal node          | high     |
    |     addrA          | connected to signal <WrAdrReg>      |          |
    |     diA            | connected to signal <DInReg>        |          |
    -----------------------------------------------------------------------
    | optimisation       | speed                               |          |
    -----------------------------------------------------------------------
    | Port B                                                              |
    |     aspect ratio   | 4096-word x 8-bit                   |          |
    |     mode           | write-first                         |          |
    |     clkB           | connected to signal <RdFlag>        | fall     |
    |     addrB          | connected to signal <RdAdrReg>      |          |
    |     doB            | connected to signal <DataOut>       |          |
    -----------------------------------------------------------------------
    | optimisation       | speed                               |          |
    -----------------------------------------------------------------------
Normalnie szacun dla WEB PACK ISE, zgadł o co mi chodzi → wykorzystał wbudowaną BLOCK RAM.
No to poeksperymentowałem z tym komponentem. Moduł testowy jest następujący:

Kod: Zaznacz cały

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;

entity dualport_ram_tb_vhd is
end dualport_ram_tb_vhd ;

architecture behavior of dualport_ram_tb_vhd is

  component dualport_ram is port ( Clock        : in std_logic ;
                                   WriteStrobe  : in std_logic ;
                                   WriteAddress : in integer range 0 to 4095 ;
                                   DataIn       : in std_logic_vector ( 7 downto 0 ) ;
                                   ReadStrobe   : in std_logic ;
                                   ReadAddress  : in integer range 0 to 4095 ;
                                   DataOut      : out std_logic_vector ( 7 downto 0 ) ) ;
  end component ;

  --Inputs
  signal Clock : std_logic := '1';
  signal WriteStrobe : std_logic := '1';
  signal WriteAddress : integer range 0 to 4095 ;
  signal DataIn :  std_logic_vector ( 7 downto 0 ) := ( others => '0' ) ;
  signal ReadStrobe : std_logic := '1' ;
  signal ReadAddress : integer range 0 to 4095 ;

  --Outputs
  signal DataOut : std_logic_vector ( 7 downto 0 ) ;

begin
  -- Instantiate the Unit Under Test (UUT)
  uut: dualport_ram port map ( Clock => Clock ,
                               WriteStrobe => WriteStrobe ,
                               WriteAddress => WriteAddress ,
                               DataIn => DataIn ,
                               ReadStrobe => ReadStrobe ,
                               ReadAddress => ReadAddress ,
                               DataOut => DataOut ) ;

  tb : process
  begin
    wait for 100 ns ;
    ReadAddress <= 0 ;
    WriteAddress <= 0 ;
    WriteStrobe <= '1' ;
    ReadStrobe <= '1' ;
    for Inx in 0 to 7 loop
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
    end loop ;
    DataIn <= "10100101" ;
    WriteAddress <= 0 ;
    for Inx in 0 to 31 loop
      WriteStrobe <= '0' ;
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
      WriteStrobe <= '1' ;
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
      WriteAddress <= WriteAddress + 1 ;
    end loop ;

    WriteAddress <= 0 ;
    DataIn <= "10100000" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 1 ;
    DataIn <= "10100001" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 2 ;
    DataIn <= "10100010" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 3 ;
    DataIn <= "10100011" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 4 ;
    DataIn <= "10100100" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 5 ;
    DataIn <= "10100101" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 6 ;
    DataIn <= "10100110" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 7 ;
    DataIn <= "10100111" ;
    WriteStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    WriteAddress <= 0 ;
    WriteStrobe <= '1' ;
    for Inx in 0 to 31 loop
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
      WriteAddress <= WriteAddress + 1 ;
    end loop ;

    ReadAddress <= 0 ;
    for Inx in 0 to 31 loop
      ReadStrobe <= '0' ;
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
      ReadStrobe <= '1' ;
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
      ReadAddress <= ReadAddress + 1 ;
    end loop ;

    DataIn <= "10111101" ;
    WriteAddress <= 5 ;
    ReadAddress <= 7 ;
    WriteStrobe <= '0' ;
    ReadStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    ReadStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    for Inx in 0 to 7 loop
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
    end loop ;

    DataIn <= "10100101" ;
    WriteAddress <= 9 ;
    ReadAddress <= 9 ;
    WriteStrobe <= '0' ;
    ReadStrobe <= '0' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;
    WriteStrobe <= '1' ;
    ReadStrobe <= '1' ;
    Clock <= '1' ;
    wait for 100 ns ;
    Clock <= '0' ;
    wait for 100 ns ;

    for Inx in 0 to 7 loop
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
      Clock <= '1' ;
      wait for 100 ns ;
      Clock <= '0' ;
      wait for 100 ns ;
    end loop ;

    wait; -- will wait forever
  end process ;

end;
Mamy tu kilka zapisów do pamięci i kilka odczytów.
vga01_02.png
Na początek kilka zapisów do pamięci: fragment pamięci wypełniony stałą wartością, później część tej pamięci została zapisana innymi danymi. Powyższa symulacja działania twierdzi, że jest OK.
Później jest kontrolny odczyt zapisanych danych. No jest to co powinno być.
vga01_03.png
Przy rozdzielonych zapisach i odczytach, zachowuje się w sposób przewidywany. To zasymulowałem jednoczesny zapis i odczyt różnych danych pod różne adresy. Układ zrobił co należy i nie pogubił się.
vga01_04.png
No dodatkowo zasymulowałem jednoczesny zapis i odczyt danych spod tego samego adresu. Wcześniej w pamięci w tym miejscu było zapisane coś. Symulator pokazał, że zaistniał zapis i jednocześnie zostały odczytane nowe dane. Tak jakby przy jednoczesnym zapisie i odczycie w tą samą komórkę priorytetem był zapis.
Mając tak podstawowy komponent (pdwuportową pamięć), szanse ciągle rosną. No z nadzieją patrzę na przyszłość i wierzę, że akcja zakończy się powodzeniem.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Kontroler VGA do systemów mikroprocesorowych

Postautor: gaweł » piątek 26 kwie 2019, 10:45

vga02_00.jpg
Timing


Podstawowym środowiskiem do eksperymentów jest kit z oferty BTC o symbolu ZL9PLD + ZL10PLD. Środowisko to zawiera układ FPGA z rodziny SPARTAN 3 (XC3S200 w obudowie VQFP100). Jego zasoby logiczne powinny wystarczyć na realizację kontrolera.
vga02_01.jpg
Nawet w zestawie jest zawarte odpowiednie złącze (które ma nawet właściwe podłączenie pinów).
vga02_02.jpg
Schemat „przyłącza” zaczerpnięty z dokumentacji jest następujący:
vga02_03.png
gdzie oporniki są już podłączone do odpowiednich wyprowadzeń (pin 50, 49, 47, 96 i 97 układu FPGA). Sam moduł (ZL10PLD) zawiera wbudowany generator do taktowania układu FPGA (w zestawie jest 3.6884 MHz). Do budowanego kontrolera potrzebny jest generator o częstotliwości 25.175 MHz. Co dziwne, to dokładnie taki walał mi się w szufladzie. Co prawda „footprint” posiadanego generatora nie pasuje do wymaganego w zestawie, ale to w końcu nie jest żaden problem. Pomimo, że FPGA wymaga poziomów logicznych 3,3V, to generator jest na 5V (już jest zawarty konwerter poziomów napięć: 74LVC14).
vga02_04.png
To kolejna zachęta do realizacji projektu.
vga02_05.jpg
W takim układzie nic mnie nie powstrzyma, by doprowadzić akcję do szczęśliwego zakończenia.
Biorąc pod uwagę, że wygenerowany obraz ma mieć 640 x 480 pikseli z 60 Hz odświeżania obrazu (tu właśnie konieczny jest generator o wspomnianej częstotliwości), to wyświetlając wiersz 80 znaków wychodzi szerokość znaku jako 8 pikseli. To oznacza, że „optyczny” odstęp między znakami w wierszu musi być zawarty w generatorze znaków (czyli znak musi zawierać się na 7 pikselach szerokości i 1 piksel odstępu między znakami). W drugą stronę jest 480 pikseli. No to jakaś mało sympatyczna wielkość. 2048 bajtów pamięci obrazu (plus drugie tyle na atrybuty każdego znaku) po 80 znaków w wierszu pozwala na uzyskanie 25 wierszy. To maksymalnie pokrywa wykorzystanie pamięci (25 * 80 = 2000). I tu pojawia się hipotetyczny problem: 480 nie dzieli się przez 25 (480 / 25 = 19.2). Gdyby użyć 19 pikseli na wiersz znakowy, to pozostaje 5 pikselowych wolnych wierszy (25 * 19 =475). Można by wygenerować jako hardare'owo puste (przykładowo 2 na górze ekranu i 3 na dole). Można by ewentualnie zrezygnować z jednego wiersza znakowego: wyświetlać 24 wiersze i wtedy wychodzi po 20 pikseli w pionie na wiersz znakowy.
Jednak chyba będę celował w maksymalne osiągi (25 wierszy). Kształt znaku wtedy musi zawrzeć się w 7 x 16 pikseli. Pozostałe 3 linie pikselowe będą stanowiły odstęp między wierszami. Tak sobie ostatnio rozmyślałem nad tematem ewentualnej organizacji i znaczenia atrybutów znakowych. Mając do dyspozycji 8 bitów (atrybutu), to można 3 bity przeznaczyć na kolor znaku i 3 bity na kolor tła. Pozostają 2 bity. W komputerowych kartach VGA są użyte po 4 bity (3 bity na kolor i 4-ty bit jako kolor rozjaśniony). No nie ma obowiązku robić identycznie i tak sobie wymyślałem, że zrobię znak mrugający: jest wyświetlany znak w określonym kolorze na określonym kolorze tła lub samo tło (1 bit i prosty multiplekser). Ostatni bit byłby atrybutem kursora wyświetlanym hardware'owo w liniach (17 .. 19) jako proste podkreślenie poniżej znaku. W komputerowej karcie VGA do sterowania kursora jest dedykowany port (z dostępem jak do portów) a pamięć obrazu jest w przestrzeni adresowej pamięci (dostęp jak do pamięci). Tu będzie wszystko jak w pamięci. Problem włączania i wyłączania kursora staje się banalny (jak również zmiana jego położenia).

Tak zrobię.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Kontroler VGA do systemów mikroprocesorowych

Postautor: gaweł » sobota 27 kwie 2019, 15:20

vga03_00.png

Kontroler VGA do systemów
mikroprocesorowych – timing obrazu


In statu nascendi – właściwie taki atrybut należałoby przypisać temu projektowi. Przedstawiam bieżące koncepcje w trakcie powstawania. Nie muszą one być trafione w dziesiątkę. Robiąc coś, w głowie powstaje jakaś koncepcja, która nie ma obowiązku być najlepszą. Dopiero jakieś „potyczki” z problemami dają jakąś wiedzę, czy przypadkiem nie skręcamy w ślepy zaułek. I tak dokładnie było w tym przypadku. Rozwiązanie sprzed wielu lat, które wyświetlało 32 znaki w 32 wierszach w sposób ukryty ominęło wiele problemów. Jeżeli zbudowany jest licznik binarny, to on „przekręca się” w sposób naturalny i nie wymaga „zawracania na początek”. Chcąc wyświetlić 80 znaków takiego patentu nie da się zastosować, bo 80 nie ma rozwinięcia czystobinarnego → znaczy, że trzeba go zawracać na siłę. Chcąc wyświetlić linię tekstową o 80 znakach w 19 liniach pikselowych (19 - o zgrozo, znów mało „okrągła liczba”), konieczny jest 19-razowy odczyt tego samego obszaru pamięci, by mając znak z każdej kolumny i numer wiersza pikselowego przechodząc przez generator znaków wyprodukować właściwe dane na wyjście wideo. Po tych 19 wierszach pikselowych odczyt danych z pamięci RAM do kolejnego wiersza tekstowego to kolejne adresy na styk z poprzednim obszarem. Same problemy, bo niby można zrobić jakiś rejestr, który byłby sumowany z dodatkowym licznikiem liczącym ciągle od zera by dać fizyczny adres w pamięci. Tu z kolei ten rejestr będzie musiał być zwiększany po każdym wierszu tekstowym o 80. Same problemy... Kombinowałem jak koń pod górę przez dwa dni i wyszło z tego g...no. Znaczy należy zmienić koncepcję, to znaczy odpuścić, odpocząć jeden dzień i … nowa koncepcja sama pojawi się w głowie. Tak też i było.
Trochę szczegółów na temat generowanego sygnału (dla 640 * 480 i 60 Hz odświeżania). Dane wygarnięte z internetu.
W poziomie (zliczane w pikselach):
  • częstotliwość taktowania: 25,175 MHz, czas trwania jednego piksela to w przybliżeniu 0,03972 μs,
  • rozbiegówka przed treścią linii: 48 pixeli = 1,90665 μs,
  • treść video: 640 pixeli = 25,4220 μs,
  • rozbiegowka za treścią linii: 16 pixeli = 0,63555 μs,
  • impuls synchronizacji H: 96 pixeli = 3,81330 μs,
  • sumarycznie, całkowity czas trwania linii : 800 pixeli = 31,777557 μs.
W pionie (zliczane w liniach pikselowych):
  • rozbiegówka przed video: 25 linii * 31,777557 0 μs = 794,4389 μs,
  • linie górnego marginesu: (8+2) 10 linii * 31,777557 μs = 317,77557 μs,
  • linie obrazu: 475 linii * 31,777557 μs = 15094,3396 μs,
  • linie dolnego marginesu: (8+3) 11 linii * 31,777557 μs = 349,5531 μs,
  • rozbiegówka za video: 2 linie * 31,777557 μs = 63,55511 μs,
  • impuls synchronizacji V: 2 linie * 31,777557 μs =63,55511 μs
  • sumarycznie: 525 linii * 31,777557 μs = 16683,217 μs * 60 sztuk = 1000993,048659 μs = 1 sek
Obraz wideo jako 480 linii pikselowych „oddaje” 2 linie na górny margines i 3 linie na dolny margines. Powstaje 475 linii pikselowych na 25 wierszy tekstowych, czyli do 19 linii pikselowych na wiersz tekstowy.
Czyli obraz to technicznie 800 x 525 pikseli, z czego widocznych jest 640 x 480 pikseli. No to może zbudować licznik pikseli na cały obraz. Potrzebny jest 800 * 525 = 420000 → 19-bitowy licznik. Mając taki licznik można precyzyjnie określić każdy moment w generowanym obrazie. Co prawda potrzebny będzie komparator 19-bitowy, ale wali mnie to. Niech się martwi o to WEBPACK i komputer, w końcu od tego jest (ma duży łeb, duże rączki to niech kombinuje).
W takim układzie przydatny będzie rejestr buforowy na jedną linię (160 bajtów: na znaki i atrybuty), który będzie obleciany 19 razy wyświetlając wiersz tekstowy. W takim układzie licznik adresowy do pamięci będzie liczył tylko do przodu w naturalny sposób.
Komponent do wygarniania wiersza tekstowego z pamięci RAM:

Kod: Zaznacz cały

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity RAMAccess is port ( Reset         : in std_logic ;
                           Clock         : in std_logic ;
                           ClearAddress  : in std_logic ;
                           RunFlag       : in std_logic ;
                           WriteStrobe   : in std_logic ;
                           WriteAddress  : in std_logic_vector ( 11 downto 0 ) ;
                           DataIn        : in std_logic_vector ( 7 downto 0 ) ;
                           CharacterData : out std_logic_vector ( 7 downto 0 ) ;
                           AttributeData : out std_logic_vector ( 7 downto 0 ) ;
                           VideoStrobe   : out std_logic ) ;
end RAMAccess ;

architecture Behavioral of RAMAccess is

  type RAMAccessAutomatStateType is ( IdleState ,
                                      ClearAddressState ,
                                      ClearAddressEndState ,
                                      SetCharReadAddressState ,
                                      GetCharDataState ,
                                      SetAttribReadAddressState ,
                                      GetAttribDataState ,
                                      StrobeOutDataLState ,
                                      StrobeOutDataHState ,
                                      CheckStatusState ) ;

  component dualport_ram port ( Clock               : in std_logic ;
                                WrToMemoryStrobe    : in std_logic ;
                                WrToMemoryAddress   : in std_logic_vector ( 11 downto 0 ) ;
                                DataToMemory        : in std_logic_vector ( 7 downto 0 ) ;
                                RdFromMemoryStrobe  : in std_logic ;
                                RdFromMemoryAddress : in std_logic_vector ( 11 downto 0 ) ;
                                DataFromMemory      : out std_logic_vector ( 7 downto 0 ) ) ;
  end component ;

  signal ReadStrobe : std_logic ;
  signal ReadAddress : std_logic_vector ( 11 downto 0 ) ;
  signal DataOut : std_logic_vector ( 7 downto 0 ) ;
  signal CharRegister : std_logic_vector ( 7 downto 0 ) ;
  signal AttribRegister : std_logic_vector ( 7 downto 0 ) ;
  signal RAMAccessAutomatState : RAMAccessAutomatStateType ;

begin

  RAMInstance : dualport_ram port map ( Clock => Clock ,
                                        WrToMemoryStrobe => WriteStrobe ,
                                        WrToMemoryAddress => WriteAddress ,
                                        DataToMemory => DataIn ,
                                        RdFromMemoryStrobe => ReadStrobe ,
                                        RdFromMemoryAddress => ReadAddress ,
                                        DataFromMemory => DataOut ) ;

  RAMAccInstance : process ( Clock , Reset , ClearAddress , RunFlag )
  begin
    if ( Reset = '0' ) or ( ClearAddress = '0' ) then
      ReadAddress <= ( others => '0' ) ;
      RAMAccessAutomatState <= IdleState ;
      VideoStrobe <= '1' ;
      ReadStrobe <= '1' ;
      CharRegister <= ( others => '0' ) ;
      AttribRegister <= ( others => '0' ) ;
    else
      if Clock'event and Clock = '1' then
        case RAMAccessAutomatState is
          when IdleState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            if ClearAddress = '0' then
              RAMAccessAutomatState <= ClearAddressState ;
            else
              if RunFlag = '0' then
                RAMAccessAutomatState <= SetCharReadAddressState ;
              else
                RAMAccessAutomatState <= IdleState ;
              end if ;
            end if ;
          when ClearAddressState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            ReadAddress <= ( others => '0' ) ;
            RAMAccessAutomatState <= ClearAddressEndState ;
          when ClearAddressEndState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            if ClearAddress = '0' then
              RAMAccessAutomatState <= ClearAddressEndState ;
            else
              RAMAccessAutomatState <= IdleState ;
            end if ;
          when SetCharReadAddressState =>
            ReadStrobe <= '0' ;
            VideoStrobe <= '1' ;
            RAMAccessAutomatState <= GetCharDataState ;
          when GetCharDataState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            CharRegister <= DataOut ;
            ReadAddress <= ReadAddress + 1 ;
            RAMAccessAutomatState <= SetAttribReadAddressState ;
          when SetAttribReadAddressState =>
            ReadStrobe <= '0' ;
            VideoStrobe <= '1' ;
            RAMAccessAutomatState <= GetAttribDataState ;
          when GetAttribDataState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            AttribRegister <= DataOut ;
            RAMAccessAutomatState <= StrobeOutDataLState ;
          when StrobeOutDataLState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '0' ;
            RAMAccessAutomatState <= StrobeOutDataHState ;
          when StrobeOutDataHState =>
            ReadAddress <= ReadAddress + 1 ;
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            RAMAccessAutomatState <= CheckStatusState ;
          when CheckStatusState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;   
            if RunFlag = '0' then
              RAMAccessAutomatState <= SetCharReadAddressState ;
            else
              RAMAccessAutomatState <= IdleState ;
            end if ;
        end case ;
      end if ;
    end if ;
  end process ;

  CharacterData <= CharRegister ;
  AttributeData <= AttribRegister ;

end Behavioral ;
Jest to komponent, który potrzebuje 7 taktów zegarowych na wygarnięcie kodu znaku i jego atrybuty z pamięci. Biorąc pod uwagę, że wyświetlanie wiersza pikselowego wymaga 80 * 8 taktów zegarowych, to wygarnięcie wiersza jest krótsze i będzie nadążne.
Testy komponentu:

Kod: Zaznacz cały

library ieee;
use ieee.std_logic_1164.ALL;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.ALL;

entity RAMAccess_tb_vhd is
end RAMAccess_tb_vhd ;

architecture behavior of RAMAccess_tb_vhd is

  component RAMAccess port ( Reset         : in std_logic ;
                             Clock         : in std_logic ;
                             ClearAddress  : in std_logic ;
                             RunFlag       : in std_logic ;
                             WriteStrobe   : in std_logic ;
                             WriteAddress  : in std_logic_vector ( 11 downto 0 ) ;
                             DataIn        : in std_logic_vector ( 7 downto 0 ) ;
                             CharacterData : out std_logic_vector ( 7 downto 0 ) ;
                             AttributeData : out std_logic_vector ( 7 downto 0 ) ;
                             VideoStrobe   : out std_logic ) ;
  end component ;

  --Inputs
  signal Reset : std_logic := '0';
  signal Clock : std_logic := '0';
  signal ClearAddress : std_logic := '1' ;
  signal RunFlag : std_logic := '1' ;
  signal WriteStrobe : std_logic := '1' ;
  signal WriteAddress : std_logic_vector ( 11 downto 0 ) := ( others => '0' ) ;
  signal DataIn : std_logic_vector ( 7 downto 0 ) := ( others => '0' ) ;

  --Outputs
  signal CharacterData : std_logic_vector ( 7 downto 0 ) ;
  signal AttributeData : std_logic_vector ( 7 downto 0 ) ;
  signal VideoStrobe : std_logic ;

begin

  uut : RAMAccess port map ( Reset => Reset ,
                             Clock => Clock ,
                             ClearAddress => ClearAddress ,
                             RunFlag => RunFlag ,
                             WriteStrobe => WriteStrobe ,
                             WriteAddress => WriteAddress ,
                             DataIn => DataIn ,
                             CharacterData => CharacterData ,
                             AttributeData => AttributeData ,
                             VideoStrobe => VideoStrobe ) ;

  tb : process
  begin
  -- Wait 100 ns for global reset to finish
    Reset <= '0' ;
    Clock <= '0' ;
    ClearAddress <= '1' ;
    RunFlag <= '1' ;
    WriteStrobe <= '1' ;
    WriteAddress <= ( others => '0' ) ;
    wait for 100 ns ;
    Reset <= '1' ;
    wait for 100 ns ;
    DataIn <= "10000000" ;
    for Inx in 0 to 2047 loop
      WriteStrobe <= '0' ;
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      WriteStrobe <= '1' ;
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      WriteAddress <= WriteAddress + 1 ;
      DataIn <= DataIn + 1 ;
      WriteStrobe <= '0' ;
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      WriteStrobe <= '1' ;
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      WriteAddress <= WriteAddress + 1 ;
      DataIn <= DataIn + 1 ;
    end loop ;
    RunFlag <= '0' ;
    for Inx in 0 to 63 loop
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      Clock <= '1' ;
    end loop ;
    RunFlag <= '1' ;
    ClearAddress <= '0' ;
    for Inx in 0 to 3 loop
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      Clock <= '1' ;
    end loop ;
    ClearAddress <= '1' ;
    RunFlag <= '0' ;
    for Inx in 0 to 639 loop
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      Clock <= '1' ;
    end loop ;
    RunFlag <= '1' ;
    for Inx in 0 to 7 loop
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
      Clock <= '1' ;
    end loop ;
    wait ;
  end process ;
end;
No to weryfikacja działania. W pierwszej fazie, to zrealizowany jest zapis inkrementowanej liczby w kolejne komórki pamięci (będzie potrzebne do weryfikacji działania wygarniania danych).
vga03_01.png
I tak do końca pamięci obrazu:
vga03_02.png
Zlecenie „wygarnięcia” wiersza tekstowego jest realizowane przez odpowiedni automat synchroniczny, Realizuje on dwa odczyty (taki dwudostęp) z kolejnych komórek pamięci i przenosi dane do lokalnego bufora na potrzeby obróbki wideo.
Zaczynając od początku obrazu, wygarniane są: 80 hex i 81 hex. W kolejnym dwudostępie jest 82 hex i 83 hex. Tak miało być (takie dane są zapisane w RAM).
vga03_03.png
Na wygarnięcie z pamięci znaku plus atrybutu automat potrzebuje 7 taktów.
vga03_04.PNG
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Kontroler VGA do systemów mikroprocesorowych

Postautor: gaweł » wtorek 30 kwie 2019, 01:04

Jeszcze nie wiem co z tego wyjdzie...
vga04_00.png

Impulsy synchro H i V

Można zbudować 19-bitowy licznik zliczający pixele. Licznik ten, wyzerowany zewnętrznym sygnałem reset będzie zliczał impulsy z zewnętrznego generatora o częstotliwości 25.175MHz. Interpretację znaczenia tego licznika przedstawia poniższy rysunek. Ponieważ jeden obraz to 525 linii pixeslowych po 800 pixeli w jednej linii, licznik ten potrzebuje 19 bitów i będzie liczył modulo 420000 (525 * 800 = 420000). W zapisie binarnym to 1100110100010100000, czyli licznik rachuje od 0 do 1100110100010011111. W VHDL'owym zapisie wygląda to następująco:

Kod: Zaznacz cały

signal PixelCounter : std_logic_vector ( 18 downto 0 ) ;
. . .
  PixelCntInstance : process ( Clock , Reset )
  begin
    if Reset = '0' then
      PixelCounter <= ( others => '0' ) ;
    else
      if Clock'event and Clock = '1' then
        if PixelCounter = "1100110100010011111" then   -- 419999
          PixelCounter <= ( others => '0' ) ;
        else
          PixelCounter <= PixelCounter + 1 ;
        end if ;
      end if ;
    end if ;
  end process ;
Ta idea pozwala na precyzyjne określenie momentu czasowego dowolnego zdarzenia na ekranie. Widoczne (jako wyświetlane) 640 * 400 pixeli na tle 800 * 525 pixeli (jako technicznych) to obszar żółty na niebieskim tle.
vga04_01.png
Przykładowo, jeżeli licznik ten doliczy się do stanu 28048 (mamy za sobą 35 wierszy pixelowych i 48 pixeli wiersza 36-ego), to jest to moment, gdy należy zacząć generować obraz wideo (w sensie treści wyświetlanej). Generowanie obrazu będzie odbywać się w oparciu o bufor znakowy wiersza tekstowego (wraz z identycznym buforem zawierającym atrybuty [kolory] wyświetlanych znaków). Można to sobie wyobrazić następująco (bufor atrybutów jest identyczny):
vga04_02.png
Taki bufor wymaga wygarnięcia danych z pamięci RAM (dwuportowej) i zapisania danych w kolejnych 8-bitowych rejestrach. Drugi bufor zawiera artybuty kolorów wyświetlanych znaków. Wygarnięcie jednej pary danych (znaku + atrybutu) wymaga 7 taktów, toteż cała akcja musi być odpowiednio wcześniej „odpalona” by przykładowo w chwili stanu licznika pixeli = 28048 zacząć generować wiersze pixelowe dla pierwszego wiersza tekstowego. Uruchomienie akcji wygarniania jest realizowane gdy sygnał „RunFlag” ma stan zera logicznego. Istotne jest precyzyjne sterowanie (w sensie czasowym) w/w sygnału by zostało wygarnięte 80 znaków i 80 atrybutów. Steruje tym komponent RunFlagGenerator.
Bazując na stanie 19-bitowego licznika pixeli, wygarnianie bufora wiersza tekstowego jest uruchamiane w następujących momentach:

Kod: Zaznacz cały

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity RunFlagGenerator is port ( Reset        : in std_logic ;
                                  PixelCounter : in std_logic_vector ( 18 downto 0 ) ;
                                  RunFlag      : out std_logic ) ;
end RunFlagGenerator ;

architecture Behavioral of RunFlagGenerator is

  signal RunFlagFlipFlop : std_logic ;
 
begin
  RunFlagGenerInstance : process ( Reset , PixelCounter )
  begin
    if Reset = '0' then
      RunFlagFlipFlop <= '1' ;
    else
      if ( PixelCounter = "0000110101001101110" ) or   -- [00] =  27246
         ( PixelCounter = "0001010010111001110" ) or   -- [01] =  42446
         ( PixelCounter = "0001110000100101110" ) or   -- [02] =  57646
         ( PixelCounter = "0010001110010001110" ) or   -- [03] =  72846
         ( PixelCounter = "0010101011111101110" ) or   -- [04] =  88046
         ( PixelCounter = "0011001001101001110" ) or   -- [05] = 103246
         ( PixelCounter = "0011100111010101110" ) or   -- [06] = 118446
         ( PixelCounter = "0100000101000001110" ) or   -- [07] = 133646
         ( PixelCounter = "0100100010101101110" ) or   -- [08] = 148846
         ( PixelCounter = "0101000000011001110" ) or   -- [09] = 164046
         ( PixelCounter = "0101011110000101110" ) or   -- [10] = 179246
         ( PixelCounter = "0101111011110001110" ) or   -- [11] = 194446
         ( PixelCounter = "0110011001011101110" ) or   -- [12] = 209646
         ( PixelCounter = "0110110111001001110" ) or   -- [13] = 224846
         ( PixelCounter = "0111010100110101110" ) or   -- [14] = 240046
         ( PixelCounter = "0111110010100001110" ) or   -- [15] = 255246
         ( PixelCounter = "1000010000001101110" ) or   -- [16] = 270446
         ( PixelCounter = "1000101101111001110" ) or   -- [17] = 285646
         ( PixelCounter = "1001001011100101110" ) or   -- [18] = 300846
         ( PixelCounter = "1001101001010001110" ) or   -- [19] = 316046
         ( PixelCounter = "1010000110111101110" ) or   -- [20] = 331246
         ( PixelCounter = "1010100100101001110" ) or   -- [21] = 346446
         ( PixelCounter = "1011000010010101110" ) or   -- [22] = 361646
         ( PixelCounter = "1011100000000001110" ) or   -- [23] = 376846
         ( PixelCounter = "1011111101101101110" ) then -- [24] = 392046
        RunFlagFlipFlop <= '0' ;
      else
        if ( PixelCounter = "0000110110010011110" ) or   -- [00] =  27806
           ( PixelCounter = "0001010011111111110" ) or   -- [01] =  43006
           ( PixelCounter = "0001110001101011110" ) or   -- [02] =  58206
           ( PixelCounter = "0010001111010111110" ) or   -- [03] =  73406
           ( PixelCounter = "0010101101000011110" ) or   -- [04] =  88606
           ( PixelCounter = "0011001010101111110" ) or   -- [05] = 103806
           ( PixelCounter = "0011101000011011110" ) or   -- [06] = 119006
           ( PixelCounter = "0100000110000111110" ) or   -- [07] = 134206
           ( PixelCounter = "0100100011110011110" ) or   -- [08] = 149406
           ( PixelCounter = "0101000001011111110" ) or   -- [09] = 164606
           ( PixelCounter = "0101011111001011110" ) or   -- [10] = 179806
           ( PixelCounter = "0101111100110111110" ) or   -- [11] = 195006
           ( PixelCounter = "0110011010100011110" ) or   -- [12] = 210206
           ( PixelCounter = "0110111000001111110" ) or   -- [13] = 225406
           ( PixelCounter = "0111010101111011110" ) or   -- [14] = 240606
           ( PixelCounter = "0111110011100111110" ) or   -- [15] = 255806
           ( PixelCounter = "1000010001010011110" ) or   -- [16] = 271006
           ( PixelCounter = "1000101110111111110" ) or   -- [17] = 286206
           ( PixelCounter = "1001001100101011110" ) or   -- [18] = 301406
           ( PixelCounter = "1001101010010111110" ) or   -- [19] = 316606
           ( PixelCounter = "1010001000000011110" ) or   -- [20] = 331806
           ( PixelCounter = "1010100101101111110" ) or   -- [21] = 347006
           ( PixelCounter = "1011000011011011110" ) or   -- [22] = 362206
           ( PixelCounter = "1011100001000111110" ) or   -- [23] = 377406
           ( PixelCounter = "1011111110110011110" ) then -- [24] = 392606
          RunFlagFlipFlop <= '1' ;
        end if ;
      end if ;
    end if ;
  end process ;

  RunFlag <= RunFlagFlipFlop ;
 
end Behavioral ;
Można to sobie wyobrazić następująco: 19-bitowy licznik napędzany sygnałem zegarowym 25,176MHz zlicza impulsy. Do tego licznika doczepiona jest „ogromna logika kombinacyjna”, która steruje ustawianiem i zerowaniem zwykłego zatrzasku. Wyjścia zatrzasku (sygnał RunFlag) aktywuje wygarnianie kolejnych 80 znaków z pamięci RAM do bufora.
vga04_03.png
Wygarnianiem zajmuje się specjalny komponent, który wygląda następująco:

Kod: Zaznacz cały

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity RAMAccess is port ( Reset         : in std_logic ;
                           Clock         : in std_logic ;
                           ClearAddress  : in std_logic ;
                           RunFlag       : in std_logic ;
                           WriteStrobe   : in std_logic ;
                           WriteAddress  : in std_logic_vector ( 11 downto 0 ) ;
                           DataIn        : in std_logic_vector ( 7 downto 0 ) ;
                           CharacterData : out std_logic_vector ( 7 downto 0 ) ;
                           AttributeData : out std_logic_vector ( 7 downto 0 ) ;
                           VideoStrobe   : out std_logic ) ;
end RAMAccess ;

architecture Behavioral of RAMAccess is

  type RAMAccessAutomatStateType is ( IdleState ,
                                      ClearAddressState ,
                                      ClearAddressEndState ,
                                      SetCharReadAddressState ,
                                      GetCharDataState ,
                                      SetAttribReadAddressState ,
                                      GetAttribDataState ,
                                      StrobeOutDataLState ,
                                      StrobeOutDataHState ,
                                      CheckStatusState ) ;

  component dualport_ram port ( Clock               : in std_logic ;
                                WrToMemoryStrobe    : in std_logic ;
                                WrToMemoryAddress   : in std_logic_vector ( 11 downto 0 ) ;
                                DataToMemory        : in std_logic_vector ( 7 downto 0 ) ;
                                RdFromMemoryStrobe  : in std_logic ;
                                RdFromMemoryAddress : in std_logic_vector ( 11 downto 0 ) ;
                                DataFromMemory      : out std_logic_vector ( 7 downto 0 ) ) ;
  end component ;

  signal ReadStrobe : std_logic ;
  signal ReadAddress : std_logic_vector ( 11 downto 0 ) ;
  signal DataOut : std_logic_vector ( 7 downto 0 ) ;
  signal CharRegister : std_logic_vector ( 7 downto 0 ) ;
  signal AttribRegister : std_logic_vector ( 7 downto 0 ) ;
  signal RAMAccessAutomatState : RAMAccessAutomatStateType ;

begin

  RAMInstance : dualport_ram port map ( Clock => Clock ,
                                        WrToMemoryStrobe => WriteStrobe ,
                                        WrToMemoryAddress => WriteAddress ,
                                        DataToMemory => DataIn ,
                                        RdFromMemoryStrobe => ReadStrobe ,
                                        RdFromMemoryAddress => ReadAddress ,
                                        DataFromMemory => DataOut ) ;

  RAMAccInstance : process ( Clock , Reset , ClearAddress , RunFlag )
  begin
    if ( Reset = '0' ) or ( ClearAddress = '0' ) then
      ReadAddress <= ( others => '0' ) ;
      RAMAccessAutomatState <= IdleState ;
      VideoStrobe <= '1' ;
      ReadStrobe <= '1' ;
      CharRegister <= ( others => '0' ) ;
      AttribRegister <= ( others => '0' ) ;
    else
      if Clock'event and Clock = '1' then
        case RAMAccessAutomatState is
          when IdleState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            if ClearAddress = '0' then
              RAMAccessAutomatState <= ClearAddressState ;
            else
              if RunFlag = '0' then
                RAMAccessAutomatState <= SetCharReadAddressState ;
              else
                RAMAccessAutomatState <= IdleState ;
              end if ;
            end if ;
          when ClearAddressState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            ReadAddress <= ( others => '0' ) ;
            RAMAccessAutomatState <= ClearAddressEndState ;
          when ClearAddressEndState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            if ClearAddress = '0' then
              RAMAccessAutomatState <= ClearAddressEndState ;
            else
              RAMAccessAutomatState <= IdleState ;
            end if ;
          when SetCharReadAddressState =>
            ReadStrobe <= '0' ;
            VideoStrobe <= '1' ;
            RAMAccessAutomatState <= GetCharDataState ;
          when GetCharDataState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            CharRegister <= DataOut ;
            ReadAddress <= ReadAddress + 1 ;
            RAMAccessAutomatState <= SetAttribReadAddressState ;
          when SetAttribReadAddressState =>
            ReadStrobe <= '0' ;
            VideoStrobe <= '1' ;
            RAMAccessAutomatState <= GetAttribDataState ;
          when GetAttribDataState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            AttribRegister <= DataOut ;
            RAMAccessAutomatState <= StrobeOutDataLState ;
          when StrobeOutDataLState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '0' ;
            RAMAccessAutomatState <= StrobeOutDataHState ;
          when StrobeOutDataHState =>
            ReadAddress <= ReadAddress + 1 ;
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;
            RAMAccessAutomatState <= CheckStatusState ;
          when CheckStatusState =>
            ReadStrobe <= '1' ;
            VideoStrobe <= '1' ;   
            if RunFlag = '0' then
              RAMAccessAutomatState <= SetCharReadAddressState ;
            else
              RAMAccessAutomatState <= IdleState ;
            end if ;
        end case ;
      end if ;
    end if ;
  end process ;

  CharacterData <= CharRegister ;
  AttributeData <= AttribRegister ;

end Behavioral ;
Jest to automat synchroniczny, który adresuje kolejne komórki w pamięci RAM i jako dane znaku i atrybutu udostępnia na zewnątrz (komponentu). Realizuje tą operację tak długo, jak długo sygnał „RunFlag” ma wartość logicznego zera (dlatego istotne jest precyzyjne sterowanie tym sygnałem, które jest realizowane w oparciu o licznik pixeli).
Składowaniem wygarniętych danych zajmuje się inny automat synchroniczny, który wygląda następująco:

Kod: Zaznacz cały

  type StobeBufferAutomatStateType is ( SBIdleState ,
                                        SBClrIndexState ,
                                        SBCheckReadyState ,
                                        SBStoreState ,
                                        SBIncrementState ,
                                        SBCheckEndState ) ;

  type BufferType is array ( 0 to 79 ) of std_logic_vector ( 7 downto 0 ) ;

. . .

  signal CharBuffer : BufferType ;
  signal AttribBuffer : BufferType ;
  signal BufferIndex : integer range 0 to 127 ;
  signal StoreBufferAutomatState : StobeBufferAutomatStateType ;
  signal ClearAddress : std_logic ;
  signal RunFlag : std_logic ;

  signal CharacterData : std_logic_vector ( 7 downto 0 ) ;
  signal AttributeData : std_logic_vector ( 7 downto 0 ) ;
  signal VideoStrobe : std_logic ;

  signal WriteStrobe : std_logic ;
  signal CharRegister : std_logic_vector ( 7 downto 0 ) ;
  signal AttribRegister : std_logic_vector ( 7 downto 0 ) ;

. . .

  StoreBufferInstance : process ( Clock , Reset )
  begin
    if Reset = '0' then
      StoreBufferAutomatState <= SBIdleState ;
      BufferIndex <= 0 ;
    else
      if Clock'event and Clock = '1' then
        case StoreBufferAutomatState is
          when SBIdleState =>
            if RunFlag = '1' then
              StoreBufferAutomatState <= SBIdleState ;
            else
              StoreBufferAutomatState <= SBClrIndexState ;
            end if ;
          when SBClrIndexState =>
            BufferIndex <= 0 ;
            StoreBufferAutomatState <= SBCheckReadyState ;
          when SBCheckReadyState =>
            if RunFlag = '0' then
              if VideoStrobe = '0' then
                StoreBufferAutomatState <= SBStoreState ;
              else
                StoreBufferAutomatState <= SBCheckReadyState ;
              end if ;
            else
              StoreBufferAutomatState <= SBIdleState ;
            end if ;
          when SBStoreState =>
            CharBuffer ( BufferIndex ) <= CharacterData ;
            AttribBuffer ( BufferIndex ) <= AttributeData ;
            if RunFlag = '0' then
              StoreBufferAutomatState <= SBIncrementState ;
            else
              StoreBufferAutomatState <= SBIdleState ;
            end if ;
          when SBIncrementState =>
            BufferIndex <= BufferIndex + 1 ;
            if RunFlag = '0' then
              StoreBufferAutomatState <= SBCheckEndState ;
            else
              StoreBufferAutomatState <= SBIdleState ;
            end if ;
          when SBCheckEndState =>
            if VideoStrobe = '0' then
              StoreBufferAutomatState <= SBCheckEndState ;
            else
              StoreBufferAutomatState <= SBCheckReadyState ;
            end if ;
        end case ;
      end if ;
    end if ;
  end process ;
Całą tą akcję można sobie przetestować. Oczywiście, by móc wysnuć właściwe wnioski z przebiegu rzeczywistości, cała pamięć wcześniej jest zapisana „znanymi danymi”, czyli zawiera inkrementowane dane (zaczynając przykładowo od 80 hex). Dane te (w sumie 4000 bajtów) kilka razy się przekręcą.
Sygnały testowe są następujące (chwilowo, co ma swoje odbicie w komponencie testowym, występują „lewe” sygnały test1..test8, do których są podłączone jeszcze nie do końca zrealizowane funkcje generujące sygnały wyjściowe, jeżeli nie zostaną „zutylizowane” w komponencie, to synteza VHDL'a usunie całe bloki, gdyż nie są wykorzystywane, toteż by temu zapobiec, niedokończone jeszcze elementy są jakkolwiek wypuszczone jako sygnały na zewnątrz, by zapobiec „redukcji”):

Kod: Zaznacz cały

library ieee;
use ieee.std_logic_1164.ALL;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.ALL;

entity VGAController_tb is
end VGAController_tb ;

architecture behavior of VGAController_tb is

  component VGAController port ( Reset      : in std_logic ;
                                 Clock      : in std_logic ;
                                 DataBus    : in std_logic_vector ( 7 downto 0 ) ;
                                 AddressBus : in std_logic_vector ( 11 downto 0 ) ;
                                 ChipSelect : in std_logic ;
test1     : out std_logic ;
test2     : out std_logic ;
test3     : out std_logic ;
test4     : out std_logic ;
test5     : out std_logic ;
test6     : out std_logic ;
test7     : out std_logic ;
test8     : out std_logic ;
                                 RColor     : out std_logic ;
                                 GColor     : out std_logic ;
                                 BColor     : out std_logic ;
                                 HSyncPulse : out std_logic ;
                                 VSyncPulse : out std_logic ) ;
  end component ;

  --Inputs
  signal Reset : std_logic := '0';
  signal Clock : std_logic := '0';
  signal DataBus : std_logic_vector ( 7 downto 0 ) := ( others => '0' ) ;
  signal AddressBus : std_logic_vector ( 11 downto 0 ) := ( others => '0' ) ;
  signal ChipSelect : std_logic := '1' ;

  --Outputs
  signal RColor : std_logic ;
  signal GColor : std_logic ;
  signal BColor : std_logic ;
  signal HSyncPulse : std_logic ;
  signal VSyncPulse : std_logic ;
  signal test1 : std_logic ;
  signal test2 : std_logic ;
  signal test3 : std_logic ;
  signal test4 : std_logic ;
  signal test5 : std_logic ;
  signal test6 : std_logic ;
  signal test7 : std_logic ;
  signal test8 : std_logic ;

begin

  uut : VGAController port map ( Reset => Reset ,
                                 Clock => Clock ,
                                 DataBus => DataBus ,
                                 AddressBus => AddressBus ,
                                 ChipSelect => ChipSelect ,
test1 => test1 ,
test2 => test2 ,
test3 => test3 ,
test4 => test4 ,
test5 => test5 ,
test6 => test6 ,
test7 => test7 ,
test8 => test8 ,
                                 RColor => RColor ,
                                 GColor => GColor ,
                                 BColor => BColor ,
                                 HSyncPulse => HSyncPulse ,
                                 VSyncPulse => VSyncPulse ) ;

  tb : process
  begin
  -- Wait 100 ns for global reset to finish
    Reset <= '0' ;
    Clock <= '0' ;
    DataBus <= "10000000" ;
    AddressBus <= "000000000000" ;
    ChipSelect <= '1' ;
    wait for 100 ns ;
    Reset <= '1' ;
    wait for 100 ns ;
    for Inx1 in 0 to 4000 loop
      ChipSelect <= '0' ;
      for Inx2 in 0 to 2 loop
        Clock <= '1' ;
        wait for 20 ns ;
        Clock <= '0' ;
        wait for 20 ns ;
      end loop ;
      ChipSelect <= '1' ;
      for Inx2 in 0 to 2 loop
        Clock <= '1' ;
        wait for 20 ns ;
        Clock <= '0' ;
        wait for 20 ns ;
      end loop ;
      DataBus <= DataBus + 1 ;
      AddressBus <= AddressBus + 1 ;
    end loop ;
    for Inx2 in 0 to 430000 loop
      Clock <= '1' ;
      wait for 20 ns ;
      Clock <= '0' ;
      wait for 20 ns ;
    end loop ;
    wait ;
  end process ;
end;
W pierwszej fazie do pamięci są zapisane dane. W symulacji wygląda to tak:
vga04_04.png
Zaczynając od zapisu bajtu o wartości 80 hex pod adres 0 w pamięci RAM, aż do adresu 4000 (2000 kodów znaków i 2000 ich atrybutów).
vga04_05.png
Symulacja wygenerowania jednego kompletnego obrazu.
vga04_06.png
Pokazuje ona sygnały wygarniania danych z pamięci do lokalnego bufora i jest tego 25 sztuk.
vga04_07.png
Robiąc powiększenie:
vga04_08.png
Pierwszy wiersz zawiera właściwe dane (wiadomo, co jest zapisane w RAM):
vga04_09.png
Po zakończeniu akcji wygarniania, można przejrzeć zawartość bufora. Jest to co powinno.
vga04_10.png
Bufor pierwszego wiersza kończy się zapisem bajtu o wartości 1E hex.
vga04_11.png
Kolejny wiersz tekstowy (nowa zawartość bufora), zaczyna się od 20 hex → jest OK (poprzedni zakończył się na 1E hex a kody znaków są inkrementowane co 2 [tak jak i atrybuty: znaki mają zawartości parzyste, atrybuty nieparzyste]).
vga04_12.png
Przy generowaniu obrazu istotna jest następująca informacja: aktualnie jesteśmy w obrębie ramki (poza obszarem wyświetlania samej treści ekranu) lub poza nią. Wyprodukowane są dwa sygnały, które przyjmują stan logicznej jedynki jeżeli „plamka kineskopu” jest o obszarze ramki: w sensie pionowym i poziomym.
Mając w perspektywie wklepanie dwa razy po 525 liczb binarnych (a właściwie to jeszcze drugie tyle dla impulsów synchronizacji poziomej), to wykombinowałem sobie inne rozwiązanie. Wykorzystanie licznika binarnego pixeli jest kłopotliwe, bo liczba pixeli na jeden wiersz nie ma ładnego rozwinięcia binarnego, ale ma ładne rozwinięcie dziesiętne. Postanowiłem dorobić licznik pixeli dla jednej linii pixelowej zliczany jako BCD (3 tetrady). Dokładnie, to liczy od: 0000, 0000, 0000 binarnie do 0111, 1001, 1001 binarnie (od 0, 0, 0 do 7, 9, 9). Margines, w sensie poziomym, jest wtedy, jeżeli ten licznik ma mniej niż 048 (tetrady: 0000, 0100, 1000) i więcej niż 687 (tetrady: 0110, 1000, 0111). Na identycznej zasadzie jest generowany impuls synchronizacji H: jeżeli tetrady BCD są w odpowiednim przedziale.

Kod: Zaznacz cały

  HBorderInstance : process ( Reset , BCD1 , BCD10 , BCD100 )
  begin
    if Reset = '0' then
      HBorderFlipFlop <= '1' ;
    else
      if ( BCD100 = "0000" ) and ( BCD10 = "0100" ) and ( BCD1 = "0111" ) then -- 47
        HBorderFlipFlop <= '0' ;
      else
        if ( BCD100 = "0110" ) and ( BCD10 = "1000" ) and ( BCD1 = "0111" ) then  -- 687
          HBorderFlipFlop <= '1' ;
        end if ;
      end if ;
    end if ;
  end process ;
Impulsy synchronizacji: to impuls synchronizacji poziomej H (jest ich 525 na jeden obraz) i pionowej V (1 na obraz).

Kod: Zaznacz cały

  HSyncInstance : process ( Reset , BCD1 , BCD10 , BCD100 )
  begin
    if Reset = '0' then
      HSyncFlipFlop <= '1' ;
    else
      if ( BCD100 = "0111" ) and ( BCD10 = "0000" ) and ( BCD1 = "0100" ) then
        HSyncFlipFlop <= '0' ;
      else
        if ( BCD100 = "0111" ) and ( BCD10 = "1001" ) and ( BCD1 = "1001" ) then
          HSyncFlipFlop <= '1' ;
        end if ;
      end if ;
    end if ;
  end process ;
Impuls synchronizacji pionowej jest już zrealizowany w oparciu o binarny licznik pixeli. Do wygenerowania impulsu stosowane są dwa stany progowe licznika:

Kod: Zaznacz cały

  VSyncInstance : process ( Reset , PixelCounter )
  begin
    if Reset = '0' then
      VSyncPulseFlipFlop <= '1' ;
    else
      if ( PixelCounter = "1100110001001100000" ) then -- 418400
        VSyncPulseFlipFlop <= '0' ;
      else
        if ( PixelCounter = "1100110100010011111" ) then -- 419999
          VSyncPulseFlipFlop <= '1' ;
        end if ;
      end if ;
    end if ;
  end process ;
Symulacja impulsów synchronizacji „testowo” wygląda następująco:
vga04_13.png
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Kontroler VGA do systemów mikroprocesorowych

Postautor: gaweł » czwartek 02 maja 2019, 19:46

Generacja sygnału wizyjnego

Wygenerowanie kolorowego sygnału wizyjnego sprowadza się do prymitywnego przetwornika cyfra → analog (jest zrealizowany jako dzielnik napięcia bazujący na potencjometrycznym połączeniu dwóch rezystorów → jednobitowy DAC). Z punktu widzenia monitora VGA na jego wejście należy podać sygnał analogowy (po przejściu przez dzielnik) dla każdej składowej koloru oddzielnie. Stąd komponent główny kontrolera ma wyjściowe sygnały: RColor, GColor, BColor oraz dodatkowy BrightColor (jako dodatkowy sygnał „rozjaśnienia” tworzonego koloru pixela, można go jakoś ująć w przetworniku DAC i wtedy uzyska się 16 kolorów, można ten sygnał zignorować, co pozwoli na uzyskanie 8 kolorów).
Pamięć obrazu jest zorganizowana w ten sposób, że na adresach parzystych zapisany jest kod znaku i na kolejnym (już nieparzystych) zapisany jest jego atrybut. W głowie pojawiło się kilka różnych koncepcji rozwiązania znaczenia atrybutów ale ostatecznie zdecydowałem się na opisane poniżej. Najmłodsze bity dotyczą koloru znaku (koloru jeżeli pixel w foncie jest włączony), najstarsze bity dotyczą koloru tła (koloru jeżeli pixel w foncie nie jest włączony). Przyporządkowanie pozycji bitowych jest następujące:
  • bit B7 – bit rozjaśnienia koloru tła,
  • bit B6 – włączenie składowej koloru R tła (czerwonego),
  • bit B5 – włączenie składowej koloru G tła (zielonego),
  • bit B4 – włączenie składowej koloru B tła (niebieskiego),
  • bit B3 – bit rozjaśnienia koloru pixela,
  • bit B2 – włączenie składowej koloru R pixela,
  • bit B1 – włączenie składowej koloru G pixela,
  • bit B0 – włączenie składowej koloru B pixela.

Generowaniem sygnału wideo zajmuje się odpowiedni automat. Odczekuje on aż „plamka kineskopu” minie cały górny margines. Wtedy automat przechodzi do kolejnego stanu związanego z odczekaniem marginesu z lewej strony i przechodzi do cyklu stanów związanych z odliczaniem wierszy tekstowych, linii pixelowych w każdym wierszu tekstowym, pixeli w obrębie każdego znaku, wyczajenia, czy należy do obrazu dogenerować kursor (ma od groma różnych zajęć). Na końcowy sukces składa się realizacja wszystkich i to w dodatku realizowanych w ściśle określonej kolejności. Więc po kolei.
Każdy wiersz tekstowy, jako wyekstrahowany z pamięci RAM bufor 80 znaków oraz atrybutów, wymaga 19-krotnego przejrzenia (znak jest wyświetlany jako 8 * 19 pixeli). Z tych 19 linii pixeliwoych 16 odzwierciedla postać znaku, pozostałe 3 linie stanowią naturalny odstęp między wierszami (inaczej wszystko będzie się zlewać) oraz jest miejscem, gdzie wyświetlany jest kursor (jako kreska o grubości 2 linii pixelowych wyświetlanych pod znakiem). Jej kolor jest determinowany zawartością odpowiedniego rejestru. Ponieważ przestrzeń adresowa pamięci RAM wynosi 4096 a sam wyświetlany tekst zajmuje 4000 bajtów, powstaje „niewykorzystany” obszar pamięci. W tych nieużywanych lokacjach zamapowane są dodatkowe rejestry: rejestr koloru kursora, rejestr pozycji x-owej kursora oraz rejestr pozycji y-owej kursora (można sobie rzecz jasna nawymyślać kilka innych „tajnych” rejestrów). Zapis, z magistrali danych, pod określone adresy będą prowadzić zamiast do pamięci RAM, w odpowiednie rejestry mające znaczenie dla automatu generującego obraz. To z kolei implikuje utworzenie prostego innego automatu zarządzającego zapisem do kontrolera. Automat ten wykrywa „specjalne” kombinacje adresowe i w tych przypadkach realizuje zapis do właściwych rejestrów. W innych przypadkach zapisy idą w pamięć RAM. Przy okazji sygnał Reset dla całego kontrolera ustawia zawartość tych rejestrów na wielkości default (pozycja kursora jest: 0, 0 oraz kursor ma kolor: rozjaśniony biały).

Kod: Zaznacz cały

  UserWriteInstance : process ( Clock , Reset )
  begin
    if Reset = '0' then
      UserWriteAutomatState <= UWIdleState ;
      UserAddress <= ( others => '0' ) ;
      UserData <= ( others => '0' ) ;
      WriteStrobe <= '1' ;
      CursorColorReg <= "00001111" ;
      CursorXPos <= ( others => '0' ) ;
      CursorYPos <= ( others => '0' ) ;
    else
      if Clock'event and Clock = '1' then
        case UserWriteAutomatState is
          when UWIdleState =>
            WriteStrobe <= '1' ;
            if ChipSelect = '0' then
              UserWriteAutomatState <= UWAddressState ;
            else
              UserWriteAutomatState <= UWIdleState ;
            end if ;
          when UWAddressState =>
            WriteStrobe <= '1' ;
            UserAddress <= AddressBus ;
            UserData <= DataBus ;
            UserWriteAutomatState <= UWWriteState ;
          when UWWriteState =>
            if UserAddress = "111111111111" then
              CursorColorReg <= UserData ;
              UserWriteAutomatState <= UWEndAccessState ;
            else
              if UserAddress = "111111111110" then
                CursorXPos <= UserData ;
                UserWriteAutomatState <= UWEndAccessState ;
              else
                if UserAddress = "111111111101" then
                  CursorYPos <= UserData ;
                  UserWriteAutomatState <= UWEndAccessState ;
                else
                  WriteStrobe <= '0' ;
                  UserWriteAutomatState <= UWWaitState ;
                end if ;
              end if ;
            end if ;
          when UWWaitState =>
            WriteStrobe <= '0' ;
            UserWriteAutomatState <= UWEndAccessState ;
          when UWEndAccessState =>
            WriteStrobe <= '1' ;
            if ChipSelect = '0' then
              UserWriteAutomatState <= UWEndAccessState ;
            else
              UserWriteAutomatState <= UWIdleState ;
            end if ;
        end case ;
      end if ;
    end if ;
  end process ;

Generowanie sygnału wizyjnego to następujący automat:

Kod: Zaznacz cały

  SerializerInstance : process ( Clock , Reset , BCD1 , BCD10 , BCD100 )
  begin
    if Reset = '0' then
      SerializerAutomatState <= SerIdleState ;
      RowCounter <= ( others => '0' ) ;
      SerialDataRegister <= ( others => '0' ) ;
      SerialAtribRegister <= ( others => '0' ) ;
      CursorYCounter <= ( others => '0' ) ;
      CursorXCounter <= ( others => '0' ) ;
      ColumnCounter <= ( others => '0' ) ;
    else
      if Clock'event and Clock = '1' then
        case SerializerAutomatState is
          when SerIdleState =>
            if VBorderFlipFlop = '1' then
              SerializerAutomatState <= SerIdleState ;
            else
              RowCounter <= ( others => '0' ) ;
              CursorYCounter <= ( others => '0' ) ;
              SerializerAutomatState <= SerialWaitOnStartState ;
            end if ;
          when SerialWaitOnStartState =>
            if ( BCD100 = "0000" ) and ( BCD10 = "0100" ) and ( BCD1 = "0101" ) then
              SerialiseBufferIndex <= ( others => '0' ) ;
              CursorXCounter <= ( others => '1' ) ;
              SerializerAutomatState <= SerStrobeIndex ;
            else
              SerializerAutomatState <= SerialWaitOnStartState ;
            end if ;
          when SerStrobeIndex =>
            if VBorderFlipFlop = '1' then
              SerializerAutomatState <= SerIdleState ;
            else
              SerialDataRegister <= CharBuffer ( CONV_INTEGER ( SerialiseBufferIndex ) ) ;
              SerialAtribRegister <= AttribBuffer ( CONV_INTEGER ( SerialiseBufferIndex ) ) ;
              SerializerAutomatState <= SerIncIndexState ;
              ColumnCounter <= ( others => '0' ) ;
              CursorXCounter <= CursorXCounter  + 1 ;
            end if ;
          when SerIncIndexState =>
            if SerialiseBufferIndex = 80 then
              ColumnCounter <= ColumnCounter + 1 ;
              SerializerAutomatState <= SerIncrementRowState ;
            else
              ColumnCounter <= ColumnCounter + 1 ;
              SerialiseBufferIndex <= SerialiseBufferIndex + 1 ;
              SerializerAutomatState <= SerWaitState ;
            end if ;
          when SerWaitState =>
            if ColumnCounter = "110" then
              if SerialiseBufferIndex = 80 then
                ColumnCounter <= ColumnCounter + 1 ;
                SerializerAutomatState <= SerIncrementRowState ;
              else
                ColumnCounter <= ColumnCounter + 1 ;
                SerializerAutomatState <= SerStrobeIndex ;
              end if ;
            else
              ColumnCounter <= ColumnCounter + 1 ;
              SerializerAutomatState <= SerWaitState ;
            end if ;
          when SerIncrementRowState =>
            if RowCounter = "10010" then -- 18
              RowCounter <= ( others => '0' ) ;
              if VBorderFlipFlop = '0' then
                CursorYCounter <= CursorYCounter  + 1 ;
                SerializerAutomatState <= SerialWaitOnStartState ;
              else
                SerializerAutomatState <= SerIdleState ;
              end if ;
            else
              RowCounter <= RowCounter + 1 ;
              SerializerAutomatState <= SerialWaitOnStartState ;
            end if ;
        end case ;
      end if ;
    end if ;
  end process ;

z dodatkową logiką kombinacyjną.
Wypracowanie niezbędnych informacji:

Kod: Zaznacz cały

  ScreenAreaFlag <= '1' when ( ( VBorderFlipFlop = '0' ) and ( HBorderFlipFlop = '0' ) ) else '0' ;
czy „plamka kineskopu” jest w obszarze wyświetlanego obrazy, czy na jakimkolwiek obrzeżu.

Kod: Zaznacz cały

 CursorFlag <= '1' when ( ( ScreenAreaFlag = '1' ) and ( CursorXCounter = CursorXPos ) and
                           ( CursorYCounter = CursorYPos ) ) else '0' ;
czy w danym wierszu tekstowym występuje kursor (jest to porównanie dwóch kilkubitowych rejestrów).

Kod: Zaznacz cały

  with ColumnCounter select
    PixelIsOn <= SerialDataRegister ( 0 ) when "111" ,
                 SerialDataRegister ( 1 ) when "110" ,
                 SerialDataRegister ( 2 ) when "101" ,
                 SerialDataRegister ( 3 ) when "100" ,
                 SerialDataRegister ( 4 ) when "011" ,
                 SerialDataRegister ( 5 ) when "010" ,
                 SerialDataRegister ( 6 ) when "001" ,
                 SerialDataRegister ( 7 ) when others ;
Powyższy zapis to multiplexer do przetworzenia danych z postaci równoległej na szeregową. Dane pochodzące z generatora znaków (chwilowo jeszcze bez implementacji, więc zastępczo jest kod znaku) są informacją określającą, czy w danej chwili „plamka” kineskopu” wskazuje włączony pixel (zaczynając od najbardziej znaczącej strony: licznik pixelowych kolumn jest inkrementowany modulo 8 a „plamka kineskopu” poruszając się od strony lewej w stronę prawą zaczyna od pozycjo najbardziej znaczącej).

Kod: Zaznacz cały

  PixelColor ( 3 ) <= SerialAtribRegister ( 3 ) when ( PixelIsOn = '1' ) else SerialAtribRegister ( 7 ) ;
  PixelColor ( 2 ) <= SerialAtribRegister ( 2 ) when ( PixelIsOn = '1' ) else SerialAtribRegister ( 6 ) ;
  PixelColor ( 1 ) <= SerialAtribRegister ( 1 ) when ( PixelIsOn = '1' ) else SerialAtribRegister ( 5 ) ;
  PixelColor ( 0 ) <= SerialAtribRegister ( 0 ) when ( PixelIsOn = '1' ) else SerialAtribRegister ( 4 ) ;
Dla każdej pozycji pixelowej znaku generowany jest sygnał wideo jako kolor pixela lub kolor tła dla danego znaku (zespół wszystkich składowych koloru).

Kod: Zaznacz cały

  CursorColor ( 3 ) <= CursorColorReg ( 3 ) when ( CursorFlag = '1' ) else SerialAtribRegister ( 7 ) ;
  CursorColor ( 2 ) <= CursorColorReg ( 2 ) when ( CursorFlag = '1' ) else SerialAtribRegister ( 6 ) ;
  CursorColor ( 1 ) <= CursorColorReg ( 1 ) when ( CursorFlag = '1' ) else SerialAtribRegister ( 5 ) ;
  CursorColor ( 0 ) <= CursorColorReg ( 0 ) when ( CursorFlag = '1' ) else SerialAtribRegister ( 4 ) ;
Analogicznie, dla każdej pozycji znakowej generowany jest sygnał wideo dla kursora jako kolor z rejestru koloru kursora (rejestr dostępny do programowania) lub w kolorze tła dla danego znaku.

Kod: Zaznacz cały

  with RowCounter select
    VideoColor <= PixelColor  when "00000" ,  --  0
                  PixelColor  when "00001" ,  --  1
                  PixelColor  when "00010" ,  --  2
                  PixelColor  when "00011" ,  --  3
                  PixelColor  when "00100" ,  --  4
                  PixelColor  when "00101" ,  --  5
                  PixelColor  when "00110" ,  --  6
                  PixelColor  when "00111" ,  --  7
                  PixelColor  when "01000" ,  --  8
                  PixelColor  when "01001" ,  --  9
                  PixelColor  when "01010" ,  -- 10
                  PixelColor  when "01011" ,  -- 11
                  PixelColor  when "01100" ,  -- 12
                  PixelColor  when "01101" ,  -- 13
                  PixelColor  when "01110" ,  -- 14
                  PixelColor  when "01111" ,  -- 15
                  CursorColor when "10000" ,  -- 16
                  CursorColor when "10001" ,  -- 17
                  SerialAtribRegister ( 7 downto 4 ) when others ;  --  18
Multiplekser wyjściowy (a dokładnie zespół 4 sztuk dla każdej składowej oddzielnie), w zależności od numeru wiersza pixelowego w wierszu teksowym, wyświetlane są pixele znaku, pixele ewentualnego kursora (w dwóch kolejnych wierszach pixelowych) oraz pixelowy wiersz pusty (w kolorze tła znaku).

Kod: Zaznacz cały

  BrightColor <= VideoColor ( 3 ) when ( ScreenAreaFlag = '1' ) else '0' ;
  RColor <= VideoColor ( 2 ) when ( ScreenAreaFlag = '1' ) else '0' ;
  GColor <= VideoColor ( 1 ) when ( ScreenAreaFlag = '1' ) else '0' ;
  BColor <= VideoColor ( 0 ) when ( ScreenAreaFlag = '1' ) else '0' ;
Logika wyjściowa sygnałów wideo: jeżeli „plamka kineskopu” jest w obszarze zewnętrznej ramki, to generowany jest sygnał z definicji czarny. W przeciwnym wypadku jest to kolor wynikający z wyświetlanego tekstu.

Symulacja daje następujące rezultaty.
vga05_01.png
Kompletny jeden obraz, można wyodrębnić 25 wierszy tekstowych. Z racji „ogromnego zagęszczenia” wiersz pierwszy i drugi zlał się w jedno. Wynika to z tego, że między każdymi innymi wierszami jest „cisza” (generowany jest sygnał w kolorze czarny w obrębie trzech ostatnich wierszy pixelowych), natomiast między pierwszym a drugim wierszem tekstowych zrealizowana jest wstawka kursora, to symulator zlał je w jedną całość.
W pierwszym wierszu wyświetlane są następujące znaki (wiersz tekstowy wpisany przez symulator, kody znaków nie mają żadnego znaczenia, istotne by były różne, bo to pozwala przeanalizować przebiegi i znaleźć bugi: tych trochę było, bo nikomu nic nie wychodzi za pierwszym razem, istotne by z zachodzących zdarzeń wyciągać właściwe wnioski i na ich podstawie wnosić właściwe korekty).
vga05_02.png
Wiersz to inkrementowane dane zaczynające się od 0 (z wyjątkiem pierwszej pozycji o zawartości 5A hex), kończące się na:
vga05_03.png
4F hex. Każdy znak ma atrybut 07 hex → normalny biały na czarnym tle.
vga05_04.png
Jak widać powyżej, jeden wiersz pixelowy, to „sieczka” na wyjściu kolorów.
Pierwszy wiersz pixelowy pierwszego wiersza tekstowego dla pierwszego znaku (o kodzie 5A hex) to:
vga05_05.png
Pierwszy wiersz pixelowy pierwszego wiersza tekstowego dla ostatniego znaku (o kodzie 4F hex) to:
vga05_06.png
Przebiegi sygnałów dla wyświetlenia kursora. Jest on ustawiony w wierszu o numerze 0 oraz kolumnie znakowej o numerze 78 (przedostatnim znakiem w wierszu).
vga05_07.png
Wyświetlanie wiersza pixelowego o numerze 16 (już po obróbce wszystkich wierszy pixelowych wiersza tekstowego) „wycina” w wyjściowej logice bramkowej sygnał kolorów. Nadawany jest kolor tła z uwzględnieniem pozycji kursora. Wykrycie sytuacji, że wymagane jest wyświetlenie kursora powoduje wtrącenie w wyjściowy sygnał wideo koloru kursora (jak na powyższej ilustracji).
vga05_08.png
Wtrącenie kursora występuje w dwóch (z trzech istniejących) kolejnych wierszach pixelowych (ostatni jest wyłącznie w kolorze tła).
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Kontroler VGA do systemów mikroprocesorowych

Postautor: gaweł » niedziela 05 maja 2019, 02:53

Generator znaków

Synteza stała się tak długa, że ...
wariacje na temat "letniej burzy"https://www.youtube.com/watch?v=ECZQUg6-TlUhttps://www.youtube.com/watch?v=DIGfO2Dgc9Yhttps://www.youtube.com/watch?v=mF1xAbAEzkohttps://www.youtube.com/watch?v=M5iDJHF_ldU


Została do wklepania tablica generatora znaków, zajęcie wielce mało interesujące, acz kolwiek niezbędne.Wygląda to mniej więcej tak (we fragmencie, bo całość się nie mieści):

Kod: Zaznacz cały

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity CharGenerator is port ( CharCode  : in std_logic_vector ( 7 downto 0 ) ;
                               RowNumber : in std_logic_vector ( 3 downto 0 ) ;
                               PixelLine : out std_logic_vector ( 7 downto 0 ) ) ;
end CharGenerator ;

architecture Behavioral of CharGenerator is

  type CharGeneratorMemType is array ( 0 to 4095 ) of std_logic_vector ( 7 downto 0 ) ;

  constant CharGeneratorMem : CharGeneratorMemType :=
                 ( "00000000" ,  -- "        " kod= 0
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 1
                   "00000000" ,  -- "        "
                   "01111110" ,  -- " ****** "
                   "10000001" ,  -- "*      *"
                   "10100101" ,  -- "* *  * *"
                   "10000001" ,  -- "*      *"
                   "10000001" ,  -- "*      *"
                   "10111101" ,  -- "* **** *"
                   "10011001" ,  -- "*  **  *"
                   "10000001" ,  -- "*      *"
                   "01111110" ,  -- " ****** "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 2
                   "00000000" ,  -- "        "
                   "01111110" ,  -- " ****** "
                   "11111111" ,  -- "********"
                   "11011011" ,  -- "** ** **"
                   "11111111" ,  -- "********"
                   "11111111" ,  -- "********"
                   "11000011" ,  -- "**    **"
                   "11100111" ,  -- "***  ***"
                   "11111111" ,  -- "********"
                   "01111110" ,  -- " ****** "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 3
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00110110" ,  -- "  ** ** "
                   "01111111" ,  -- " *******"
                   "01111111" ,  -- " *******"
                   "01111111" ,  -- " *******"
                   "01111111" ,  -- " *******"
                   "00111110" ,  -- "  ***** "
                   "00011100" ,  -- "   ***  "
                   "00001000" ,  -- "    *   "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
( . . )
                   "00000000" ,  -- "        " kod= 65
                   "00000000" ,  -- "        "
                   "00001000" ,  -- "    *   "
                   "00011100" ,  -- "   ***  "
                   "00110110" ,  -- "  ** ** "
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01111111" ,  -- " *******"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 66
                   "00000000" ,  -- "        "
                   "01111110" ,  -- " ****** "
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00111110" ,  -- "  ***** "
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "01111110" ,  -- " ****** "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 67
                   "00000000" ,  -- "        "
                   "00011110" ,  -- "   **** "
                   "00110011" ,  -- "  **  **"
                   "01100001" ,  -- " **    *"
                   "01100000" ,  -- " **     "
                   "01100000" ,  -- " **     "
                   "01100000" ,  -- " **     "
                   "01100001" ,  -- " **    *"
                   "00110011" ,  -- "  **  **"
                   "00011110" ,  -- "   **** "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 68
                   "00000000" ,  -- "        "
                   "01111100" ,  -- " *****  "
                   "00110110" ,  -- "  ** ** "
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110110" ,  -- "  ** ** "
                   "01111100" ,  -- " *****  "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 69
                   "00000000" ,  -- "        "
                   "01111111" ,  -- " *******"
                   "00110011" ,  -- "  **  **"
                   "00110001" ,  -- "  **   *"
                   "00110100" ,  -- "  ** *  "
                   "00111100" ,  -- "  ****  "
                   "00110100" ,  -- "  ** *  "
                   "00110001" ,  -- "  **   *"
                   "00110011" ,  -- "  **  **"
                   "01111111" ,  -- " *******"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 70
                   "00000000" ,  -- "        "
                   "01111111" ,  -- " *******"
                   "00110011" ,  -- "  **  **"
                   "00110001" ,  -- "  **   *"
                   "00110100" ,  -- "  ** *  "
                   "00111100" ,  -- "  ****  "
                   "00110100" ,  -- "  ** *  "
                   "00110000" ,  -- "  **    "
                   "00110000" ,  -- "  **    "
                   "01111000" ,  -- " ****   "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 71
                   "00000000" ,  -- "        "
                   "00011110" ,  -- "   **** "
                   "00110011" ,  -- "  **  **"
                   "01100001" ,  -- " **    *"
                   "01100000" ,  -- " **     "
                   "01100000" ,  -- " **     "
                   "01101111" ,  -- " ** ****"
                   "01100011" ,  -- " **   **"
                   "00110011" ,  -- "  **  **"
                   "00011101" ,  -- "   *** *"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 72
                   "00000000" ,  -- "        "
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01111111" ,  -- " *******"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 73
                   "00000000" ,  -- "        "
                   "00111100" ,  -- "  ****  "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00111100" ,  -- "  ****  "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 74
                   "00000000" ,  -- "        "
                   "00001111" ,  -- "    ****"
                   "00000110" ,  -- "     ** "
                   "00000110" ,  -- "     ** "
                   "00000110" ,  -- "     ** "
                   "00000110" ,  -- "     ** "
                   "00000110" ,  -- "     ** "
                   "01100110" ,  -- " **  ** "
                   "01100110" ,  -- " **  ** "
                   "00111100" ,  -- "  ****  "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 75
                   "00000000" ,  -- "        "
                   "01110011" ,  -- " ***  **"
                   "00110011" ,  -- "  **  **"
                   "00110110" ,  -- "  ** ** "
                   "00110110" ,  -- "  ** ** "
                   "00111100" ,  -- "  ****  "
                   "00110110" ,  -- "  ** ** "
                   "00110110" ,  -- "  ** ** "
                   "00110011" ,  -- "  **  **"
                   "01110011" ,  -- " ***  **"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 76
                   "00000000" ,  -- "        "
                   "01111000" ,  -- " ****   "
                   "00110000" ,  -- "  **    "
                   "00110000" ,  -- "  **    "
                   "00110000" ,  -- "  **    "
                   "00110000" ,  -- "  **    "
                   "00110000" ,  -- "  **    "
                   "00110001" ,  -- "  **   *"
                   "00110011" ,  -- "  **  **"
                   "01111111" ,  -- " *******"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 77
                   "00000000" ,  -- "        "
                   "11000011" ,  -- "**    **"
                   "11100111" ,  -- "***  ***"
                   "11111111" ,  -- "********"
                   "11011011" ,  -- "** ** **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 78
                   "00000000" ,  -- "        "
                   "01100011" ,  -- " **   **"
                   "01110011" ,  -- " ***  **"
                   "01111011" ,  -- " **** **"
                   "01111111" ,  -- " *******"
                   "01101111" ,  -- " ** ****"
                   "01100111" ,  -- " **  ***"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 79
                   "00000000" ,  -- "        "
                   "00011100" ,  -- "   ***  "
                   "00110110" ,  -- "  ** ** "
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "00110110" ,  -- "  ** ** "
                   "00011100" ,  -- "   ***  "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 80
                   "00000000" ,  -- "        "
                   "01111110" ,  -- " ****** "
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00111110" ,  -- "  ***** "
                   "00110000" ,  -- "  **    "
                   "00110000" ,  -- "  **    "
                   "00110000" ,  -- "  **    "
                   "01111000" ,  -- " ****   "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 81
                   "00000000" ,  -- "        "
                   "00111110" ,  -- "  ***** "
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01101011" ,  -- " ** * **"
                   "01101111" ,  -- " ** ****"
                   "00111110" ,  -- "  ***** "
                   "00000110" ,  -- "     ** "
                   "00000111" ,  -- "     ***"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 82
                   "00000000" ,  -- "        "
                   "01111110" ,  -- " ****** "
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "00111110" ,  -- "  ***** "
                   "00110110" ,  -- "  ** ** "
                   "00110011" ,  -- "  **  **"
                   "00110011" ,  -- "  **  **"
                   "01110011" ,  -- " ***  **"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 83
                   "00000000" ,  -- "        "
                   "00111110" ,  -- "  ***** "
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "00110000" ,  -- "  **    "
                   "00011100" ,  -- "   ***  "
                   "00000110" ,  -- "     ** "
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "00111110" ,  -- "  ***** "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 84
                   "00000000" ,  -- "        "
                   "11111111" ,  -- "********"
                   "11011011" ,  -- "** ** **"
                   "10011001" ,  -- "*  **  *"
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00111100" ,  -- "  ****  "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 85
                   "00000000" ,  -- "        "
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "01100011" ,  -- " **   **"
                   "00111110" ,  -- "  ***** "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 86
                   "00000000" ,  -- "        "
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "01100110" ,  -- " **  ** "
                   "00111100" ,  -- "  ****  "
                   "00011000" ,  -- "   **   "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 87
                   "00000000" ,  -- "        "
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11011011" ,  -- "** ** **"
                   "11011011" ,  -- "** ** **"
                   "11111111" ,  -- "********"
                   "01100110" ,  -- " **  ** "
                   "01100110" ,  -- " **  ** "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 88
                   "00000000" ,  -- "        "
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "01100110" ,  -- " **  ** "
                   "00111100" ,  -- "  ****  "
                   "00011000" ,  -- "   **   "
                   "00111100" ,  -- "  ****  "
                   "01100110" ,  -- " **  ** "
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 89
                   "00000000" ,  -- "        "
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "11000011" ,  -- "**    **"
                   "01100110" ,  -- " **  ** "
                   "00111100" ,  -- "  ****  "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00011000" ,  -- "   **   "
                   "00111100" ,  -- "  ****  "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 90
                   "00000000" ,  -- "        "
                   "11111111" ,  -- "********"
                   "11000011" ,  -- "**    **"
                   "10000110" ,  -- "*    ** "
                   "00001100" ,  -- "    **  "
                   "00011000" ,  -- "   **   "
                   "00110000" ,  -- "  **    "
                   "01100001" ,  -- " **    *"
                   "11000011" ,  -- "**    **"
                   "11111111" ,  -- "********"
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
----------------------------------------------------
( . . )
----------------------------------------------------
                   "00000000" ,  -- "        " kod= 255
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ,  -- "        "
                   "00000000" ) ;-- "        "
  signal PixelLineRegister : std_logic_vector ( 7 downto 0 ) ;
  signal CharGenMemAddress : std_logic_vector ( 11 downto 0 ) ;

begin

  CharGenMemAddress <= CharCode & RowNumber ;

  process
  begin
    PixelLineRegister <= CharGeneratorMem ( CONV_INTEGER ( CharGenMemAddress ) ) ;
  end process ;

  PixelLine <= PixelLineRegister ;

end Behavioral ;
To całkiem spore obciążenie dla oprogramowania dla XILINX. Synteza zrobiła się całkiem spora w czasie, więc można było posłuchać muzy. Tak jakoś ostatnio mi zeszło na "Storm" Vivaldi'ego z "Czterech pór roku - Lato". Wracając do FPGA, to (co raduje me serce), całość nadal mieści się w jednym układzie. Ale nie wszystko jest takie super. Symulacja całości już tak słabo działa, a dokładniej to... nie doczekalem sie na jej uruchomienie. No może jestem za mało cierpliwy :? . Trzeba będzie przejść już z wirtualnej rzeczywistości na prawdziwą rzeczywistość.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Kontroler VGA do systemów mikroprocesorowych

Postautor: gaweł » niedziela 05 maja 2019, 17:37

Testy generatora znaków

... z letnią burzą w tlehttps://www.youtube.com/watch?v=frtuXA9HUWMhttps://www.youtube.com/watch?v=pnyfESsYbYc

Chcąc jakkolwiek przetestować generator znaków, ot choćby, czy nie ma jakichś przesunięć o jednego piksela, postanowiłem zastąpić go w całości na generowanie jednego znaku: przykładowo znaku '*'. By synteza nie uwaliła całkiem sporej części, jakkolwiek należałoby zutylizować kod znaku przychodzący do generatora. Zastępcza postać wygląda następująco:

Kod: Zaznacz cały

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity CharGenerator is port ( CharCode  : in std_logic_vector ( 7 downto 0 ) ;
                               RowNumber : in std_logic_vector ( 3 downto 0 ) ;
                               PixelLine : out std_logic_vector ( 7 downto 0 ) ) ;
end CharGenerator ;

architecture Behavioral of CharGenerator is

  signal PixelLineRegister : std_logic_vector ( 7 downto 0 ) ;
  signal CharGenMemAddress : std_logic_vector ( 4 downto 0 ) ;
  signal xxx : std_logic ;

begin

  xxx <= '1' when ( CharCode = "00111111" ) else '0' ;
  CharGenMemAddress <= xxx & RowNumber ;
  with CharGenMemAddress select
    PixelLine <= "00000000" when "00000" ,
                 "00000000" when "00001" ,
                 "00000000" when "00010" ,
                 "00000000" when "00011" ,
                 "00000000" when "00100" ,
                 "01100110" when "00101" ,
                 "00111100" when "00110" ,
                 "11111111" when "00111" ,
                 "00111100" when "01000" ,
                 "01100110" when "01001" ,
                 "00000000" when "01010" ,
                 "00000000" when "01011" ,
                 "00000000" when "01100" ,
                 "00000000" when "01101" ,
                 "00000000" when "01110" ,
                 "00000000" when "01111" ,
                 "00000000" when "10000" ,
                 "00000000" when "10001" ,
                 "00001000" when "10010" ,
                 "00011100" when "10011" ,
                 "00110110" when "10100" ,
                 "01100011" when "10101" ,
                 "01100011" when "10110" ,
                 "01111111" when "10111" ,
                 "01100011" when "11000" ,
                 "01100011" when "11001" ,
                 "01100011" when "11010" ,
                 "00000000" when "11011" ,
                 "00000000" when "11100" ,
                 "00000000" when "11101" ,
                 "00000000" when "11110" ,
                 "00000000" when others ;

end Behavioral ;

Synteza układu już trochę trwa (ale znacząco krócej niż w w przypadku pełnego generatora). Symulacja również ma akceptowalny czas przebiegu i co najważniejsze pokazuje, że wszystko przebiega zgodnie z założeniem.
vga06_01.png
vga06_02.png
vga06_03.png
vga06_04.png
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „DIY”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 7 gości