.. _w07-io:


=================================================
Wykład 7: Wejście i wyjście, popularne interfejsy
=================================================

Data: 21.11.2018

.. toctree::

.. contents::


Bufory wejścia/wyjścia
======================

Zewnętrzne sygnały nie są podłączone bezpośrednio do wewnętrznej sieci
połączeń FPGA -- przechodzą najpierw przez bufory wejścia/wyjścia (IOB).
Ich zadania to:

1. Zmiana interfejsu elektrycznego.  FPGA pracuje wewnętrznie na ustalonym
   napięciu zasilającym (1.2V w przypadku Spartana 3E) i w technologii CMOS.
   Na zewnątrz natomiast możemy wybrać jedno z wielu wspieranych napięć
   interfejsowych i standardĂłw elektrycznych.
2. Konwersja pojedynczych sygnałów w sygnały różnicowe i z powrotem.
3. Rozdzielenie zewnętrznych sygnałów dwukierunkowych na 3 sygnały wewnętrzne
   (wejście, wyjście, włączenie wyjścia).
4. Synchronizacja danych przychodzących i wychodzących do zegara.

Bufory wejścia/wyjścia tworzone są przez narzędzia syntezy automatycznie
z portów zewnętrznych na głównym module, ale możemy też zinstancjonować
je ręcznie::

    module my_ram(
        input wire write_enable,
        input wire read_enable,
        inout wire data);

    reg storage = 0;

    wire data_in;

    // Rzadki przypadek celowej syntezy zatrzasku.
    always @(write_enable, data) begin
        if (write_enable)
            storage <= data_in;
    end

    `ifdef MANUAL

    // Wersja ręczna:

    // Bufor wejścia/wyjścia: stan pinu data zawsze jest widoczny na data_in.
    // Jeśli T == 0, bufor nadaje na pinie data wartość storage.  Jeśli T == 1,
    // bufor wyjściowy jest wyłączony i nic nie nadaje.
    IOBUF my_buffer(.IO(data), .I(storage), .O(data_in), .T(!read_enable);

    `else

    // Wersja automatyczna (rĂłwnowaĹźna):

    assign data = read_enable ? storage : 1'bz;
    assign data_in = data;

    `endif

    endmodule


Druty wiszące, pullup, pulldown
-------------------------------

Rozważmy sygnał zewnętrzny, do którego akurat nie jest podłączony żaden aktywny
bufor wyjściowy (nazywany wiszącym drutem (floating)).  Jaki będzie jego stan?

Zależy to od technologii.  W przypadku logiki TTL, stan takiego sygnału
ustabilizuje się na 1.  Jednak w przypadku logiki CMOS, stan takiego sygnału
jest nieustalony -- drut efektywnie stanie się anteną zbierającą zakłócenia
z okolicy i będzie oscylował, często zmieniając stan i powodując zwiększone
zużycie prądu na wszystkich podłączonych układach.  Z tego powodu, wiszących
drutów należy unikać.

Z drugiej strony, w przypadku sygnałów dwukierunkowych zawsze musi istnieć
krótki moment, w którym żaden z układów nie ma aktywnego wyjścia (w przeciwnym
wypadku mielibyśmy jeszcze większe problemy).

Rozwiązaniem problemu jest dołączenie elementów ciągnących w dół / ciągnących
w górę (pulldown / pullup) -- są to po prostu oporniki podłączone z drugiej
strony do masy (pulldown) bądź napięcia zasilającego (pullup), ustalające
stan sygnału jeśli nie ma żadnego aktywnego bufora wyjściowego (który jest
silniejszy od opornika).  W układach FPGA możemy wybrać jedną z 4 opcji
dla każdego sygnału zewnętrznego:

- uĹźycie wbudowanego opornika pulldown
- uĹźycie wbudowanego opornika pullup
- użycie układu keeper (pullup jeśli aktualny stan to 1, pulldown jeśli 0) --
  efektywnie utrzymuje obecny stan na sygnale
- brak wbudowanego opornika (używamy, jeśli mamy zewnętrzny pullup/pulldown
  na płytce)

Wyboru dokonujemy przez zinstancjonowanie odpowiedniego prymitywu w Verilogu
(PULLDOWN, PULLUP, KEEPER) bądź w pliku UCF.

Układy FPGA mają dużo nóżek i prawdopodobnie nie użyjemy wszystkich.  Narzędzia
syntezy automatycznie aktywują na nich pulldown.


Sygnały open-drain
------------------

Podstawowym problemem przy projektowaniu układu z sygnałami dwukierunkowymi
jest ustalenie, który układ akurat ma prawo nadawać (aby uniknąć sytuacji,
gdy jeden układ nadaje 0, a inny 1).  Dość ciekawym i powszechnym rozwiązaniem
tego problemu jest użycie sygnałów open-drain (nazywanych też open collector)
-- wspólnej linii komunikacyjnej z pullupem, na której układom wolno nadawać
tylko stan 0, bądź nic nie nadawać
(i pozwolić innemu układowi nadać 0, bądź pozwolić pullupowi podciągnąć stan
do poziomu 1).

Najprostszym przykładem użycia takiej linii są współdzielone linie przerwań
na szynie PCI -- urządzenie zgłaszające przerwanie wystawia 0, a natura linii
open-drain efektywnie tworzy bramkę logiczną AND z wszystkich urządzeń.
Bardziej skomplikowanymi przykładami użycia open-drain są szyna I2C bądź
interfejs klawiatury i myszy PS/2.


Rodzaje interfejsĂłw
===================

Interfejsy sprzętowe możemy z grubsza podzielić na następujące kategorie:

- wolne (<~10 MHz) -- uĹźywamy byle jakiego zegara i przepuszczamy wszystkie
  wejścia przez synchronizator, cykl w tą czy w tamtą stronę nic nie zmienia.
  Przykłady: wolne SPI/JTAG, UART, I2C, PS/2, EPP, przyciski/diody.
- średnie (<~200MHz) -- używamy zegara zsynchronizowanego z interfejsem.
  Przykłady: szybkie SPI/JTAG, VGA, PCI, ISA/ATA.
- szybkie (<~1GHz) -- jak wyżej, ale używamy sygnałowania różnicowego,
  ostrożnie dobieramy impedancję połączeń i terminatory, zapewniamy równej
  długości ścieżki na płytce, wyrównujemy fazę zegara, itp.  Nie ma już
  nadziei na zrobienie szyny -- tylko łącza P2P.  Przykłady: interfejsy
  do pamięci DDR*.
- bardzo szybkie (>~1GHz) -- nie ma już nadziei na interfejs równoległy,
  musimy użyć interfejsu szeregowego i odtwarzać zegar z sygnału.  Wymaga
  dedykowanych skomplikowanych blokĂłw nadawczych i odbiorczych (nie mamy
  takich w Spartanie 3E).  Przykłady: PCI Express, SATA, TMDS (DVI/HDMI),
  DisplayPort.


.. _vga:

VGA, DVI, HDMI
==============

Monitor (bądź telewizor) CRT składa się z:

- ekranu, w którym punkty świecą gdy zostaną trafione elektronem
- działka elektronowego, które strzela elektronami (a właściwie trzech, dla każdego koloru)
- magnesu odchylenia pionowego, sterującego działkiem w osi pionowej
- *potężnego* magnesu odchylenia poziomego i *potężnego* transformatora do niego
- układu sterującego powyższym

Wyświetlanie obrazu rastrowego na monitorze CRT odbywa się następująco:

- zaczynamy (arbitralnie -- proces jest cykliczny, a proces synchronizacji skomplikowany)
  od lewego gĂłrnego piksela obrazu
- wyświetlamy pierwszą linię obrazu, przesuwając działko od lewej do prawej krawędzi ekranu,
  modulując intensywność działka przez kolor piksela
- wyłączamy działko i szybko przesuwamy działko z powrotem na lewą krawędź ekranu
- włączamy działko z powrotem i wyświetlamy kolejną linię
- przez cały proces przesuwamy powoli działko w dół
- gdy dojdziemy do dołu ekranu, wyłączamy działko i przesuwamy je w górę, zaczynając cały
  proces od początku (trwa to o wiele dłużej niż powrót do lewej krawędzi ekranu)

Ponieważ monitory CRT są starą i bardzo analogową technologią, układ poziomy jest tak naprawdę
niezależny od układu pionowego i oba działają bez przerwy  -- w trakcie powrotu działka na górę
ekranu, układ poziomy cały czas jeździ między lewą a prawą krawędzią ekranu.

Monitory CRT sterowane przez złącze VGA mają następujące piny:

- R, G, B -- analogowa wartość aktualnie wyświetlanego piksela
- HSYNC -- cyfrowy sygnał powrotu do lewej krawędzi
- VSYNC -- cyfrowy sygnał powrotu do górnej krawędzi

Na płytce Basys2 mamy bieda-konwerter cyfrowo-analogowy, z 3 bitami precyzji na kolory czerwony
i zielony oraz 2 bitami pracyzji na kolor niebieski.

Aby uzyskać obraz na monitorze CRT przez VGA należy wykonać następujące kroki:

- wybrać jakąś rozdzielczość i częstotliwość odświeżania (http://martin.hinner.info/vga/timing.html,
  http://tinyvga.com/vga-timing)
- uzyskać skądś (DCM) stosowny pixel clock
- dla każdej wyświetlanej linii:

  - przez (active video) cykli wysyłać kolejne piksele obrazu, HSYNC=inactive
  - przez (front porch) cykli wysyłać czarny piksel, HSYNC=inactive (ten obszar
    jest "strefą buforową", która może być zniekształcona przez monitor
    ze względu na bliskość sygnału powrotu poziomego)
  - przez (sync pulse) cykli wysyłać czarny piksel, HSYNC=active (działko wraca na lewą stronę)
  - przez (back porch) cykli wysyłać czarny piksel, HSYNC=inactive (kolejna strefa buforowa)

- dla każdej wyświetlanej ramki:

  - przez (active video) linii wysyłać kolejne linie obrazu, VSYNC=inactive
  - przez (front porch) linii wysyłać same czarne piksele, VSYNC=inactive
  - przez (sync pulse) linii wysyłać same czarne piksele, VSYNC=active
  - przez (back porch) linii wysyłać same czarne piksele, VSYNC=inactive

- wyświetlać ramki bez przerwy w pętli (trzeba poczekać kilkadziesiąt ramek aż
  monitor się zsynchronizuje)
- pamiętać, że mapowanie wartości active i inactive na 0 i 1 zależy od wybranego trybu
  (i może być różne dla HSYNC i VSYNC)

Do tego, złącze VGA ma interfejs I2C do EEPROMu wbudowanego w monitor, w którym
opisane są możliwości monitora (tzw. EDID).  Niestety, Basys 2 nie pozwala nam dostać
się do tych sygnałów.

Aby uzyskać obraz na monitorze LCD przez interfejs VGA, należy wspólnie z nim udawać,
Ĺźe jest on monitorem CRT.

DVI
---

DVI jest złączem zawierającym, oprócz sygnałów analogowych zgodnych z VGA, sygnał cyfrowy.
Sygnał cyfrowy używa 4 par różnicowych (R, G, B, zegar), ma dokładnie 10× szybszy zegar
niż pixel clock i przesyła dokładnie te same dane co sygnał analogowy (z 8 bitami na kolor,
zakodowanymi przez kodowanie 8b10, ze specjalnymi symbolami zastępującymi sygnały
HSYNC i VSYNC).

HDMI
----

HDMI to cyfrowa część DVI na (wstecznie zgodnych) sterydach -- m.in. wysyła audio
w nieuĹźywanym obszarze danych w pionowym front/back porch.
Ogólna struktura sygnału wciąż jest taka sama jak VGA.

Praktycznie każde złącze DVI we współczesnych kartach graficznych jest tak naprawdę
źródłem sygnału HDMI.


.. _ps2:

PS/2
====

Interfejs PS/2 opiera się na dwóch sygnałach 5V open-drain:

- DATA
- CLK

Zegar interfejsu nie jest dokładnie wyspecyfikowany, ale powinien być w zakresie
10-16.7 kHz.

Komunikacja na interfejsie PS/2 jest sterowana przez urządzenie wejściowe
(mysz/klawiaturę).  Wysyłanie poleceń do urządzenia jest możliwe (np. polecenie
zapal diody na klawiaturze), ale jest dość skomplikowane.  Jeśli chcemy
tylko odbierać dane:

- nie wysyłamy nic na pinach CLK/DATA
- dopóki CLK = 1, nic się nie dzieje
- gdy CLK = 0, urządzenie transmituje bit -- należy go złapać w momencie
  zmiany z 1 na 0
- kaĹźdy bajt ma 11 bitĂłw:
  - bit startowy, równy 0 (jeśli widzimy inny, zgubiliśmy synchronizację)
  - 8 bitĂłw danych, od najniĹźszego
  - bit parzystośći (1 jeśli parzysta liczba bitów danych jest równa 1)
  - bit stopu, rĂłwny 1

Klawiatura wysyła powyższym protokołem kody klawiszy (nie są w ASCII --
są to tzw. scan codes i należy użyć tabelki https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_2).  Niektóre z nich
są 2-bajtowe (w tym wypadku pierwszy bajt to ``0xe0``).  Przy naciśnięciu
klawisza wysyłany jest jego kod, a przy puszczeniu klawisza wysyłany
jest jego kod poprzedzony bajtem ``0xf0``.

Mysz wysyła powyższym protokołem 3-bajtowe pakiety opisujące ruch myszy:

- bajt 0:
  - bit 0: stan lewego przycisku
  - bit 1: stan prawego przycisku
  - bit 2: stan środkowego przycisku
  - bit 3: zawsze równy 1 (jeśli widzimy inny stan, zgubiliśmy synchronizację pakietów)
  - bit 4: bit 8 przesunięcia w osi X
  - bit 5: bit 8 przesunięcia w osi Y
  - bit 6: jeśli 1, przesunięcie w osi X się nie zmieściło w 9 bitach
  - bit 7: jeśli 1, przesunięcie w osi Y się nie zmieściło w 9 bitach
- bajt 1: bity 0-7 przesunięcia w osi X
- bajt 2: bity 0-7 przesunięcia w osi Y

Klawiatura i mysz wysyłają również kod statusu po uruchomieniu (po którym można m.in.
rozpoznać, z którym z nich mamy do czynienia).  Niestety, nie zobaczymy go na naszej
płytce (FPGA nie zdąży się skonfigurować zanim klawiatura/mysz wystartuje).

Po pełny opis interfejsu odsyłam do:

- https://web.archive.org/web/20050120021034/http://panda.cs.ndsu.nodak.edu:80/~achapwes/PICmicro/PS2/ps2.htm
- https://web.archive.org/web/20041117095622/http://panda.cs.ndsu.nodak.edu:80/~achapwes/PICmicro/mouse/mouse.html
- https://web.archive.org/web/20050120022433/http://panda.cs.ndsu.nodak.edu:80/~achapwes/PICmicro/keyboard/atkeyboard.html
- https://www.digikey.com/eewiki/pages/viewpage.action?pageId=28278929
- https://wiki.osdev.org/PS/2_Keyboard
- https://wiki.osdev.org/PS/2_Mouse


.. _epp:

EPP
===

EPP (enhanced parallel port) był oryginalnie jednym z kilku trybów, w których mógł pracować
stary port równoległy komputerów PC (zwany również IEEE 1284, LPT, bądź portem drukarki).
Z jakiegoś powodu Digilent uznał za stosowne uczynić z niego interfejs używany na niektórych
płytkach do komunikacji między komputerem a układem FPGA.

Piny interfejsu są następujące:

- EppDB[7:0] -- dwukierunkowa szyna danych
- EppAstb -- z komputera do FPGA, komputer żąda przesyłu adresu, gdy ten pin jest równy 0
- EppDstb -- z komputera do FPGA, komputer żąda przesyłu danych, gdy ten pin jest równy 0
- EppWR -- z komputera do FPGA, komputer pisza dane gdy rĂłwne 0, czyta gdy rĂłwne 1
- EppWait -- z FPGA do komputera, 1 oznacza zakończenie przesyłu

Protokół jest całkowicie sterowany przez komputer i działa następująco:

1. Dopóki Epp*stb są równe 1, nic się nie dzieje.  FPGA powinna nie wysyłać nic na EppDB i wysyłać
   0 na EppWait.
2. Gdy zauważymy 0 na EppAstb lub EppDstb, komputer żąda transakcji.  Jeśli transakcja jest zapisem,
   powinniśmy złapać dane (bądź adres) z pinów EppDB, a jeśli jest odczytem, powinniśmy wysłać dane
   (bądź aktualny adres) na piny EppDB.
3. Gdy wykonamy zapis bądź odczyt (i, w przypadku odczytu, zaczniemy wysyłać dane na EppDB),
   zaczynamy wysyłać 1 na linii EppWait.
4. Czekamy aĹź EppAstb/EppDstb wrĂłci do stanu 1.
5. Jeśli to był odczyt, przestajemy wysyłać dane na EppDB.
6. Przywracamy EppWait do stanu 0.

W standardowym użyciu portu EPP, komputer zawsze zapisuje adres, a potem zapisuje bądź odczytuje dane,
pozwalając nam na realizację 256 8-bitowych rejestrów danych.


RS-232
======

RS-232, zwany rĂłwnieĹź portem szeregowym lub UARTem, jest prostym interfejsem oryginalnie uĹźywanym
do podłączania dalekopisów do komputerów, być może za pośrednictwem modemów i linii telefonicznych.

Ma następujące linie sygnałowe:

- TxD (komputer do dalekopisu), RxD (dalekopis do komputera) -- dane szeregowe
- RTS (komputer do dalekopisu), CTS (dalekopis do komputera) -- kontrola przepływu
- DTR (komputer do dalekopisu), DSR (dalekopis do komputera) -- sygnały gotowości do pracy
- DCD (modem do komputera) -- stan obecności sygnału
- RI (modem do komputera) -- przychodzące połączenie telefoniczne

Zazwyczaj używa się tylko TxD i RxD.

Standard RS-232 specyfikuje standard elektryczny następująco:

- 0: przesyłane jako od +3V do +15V względem masy
- 1: przesyłane jako od -3V do -15V względem masy

Jest to zbyt duże napięcie, by podłączyć je do FPGA czy większości mikrokontrolerów.
Dlatego też zawsze używa się konwertera napięć (bądź, w przypadku komunikacji w ramach
jednej płytki, w ogóle używa się tylko niskich napięć).

Na liniach danych (TxD i RxD) komunikacja przebiega następująco:

1. Nadawca i odbiorca muszą używać tych samych parametrów przesyłu (standard nie przewiduje żadnej możliwości wykrycia ich).
2. Komunikacja jest symetryczna (TxD wygląda tak samo jak RxD).
3. Dopóki nic się nie dzieje, linia jest w stanie 1.
4. Gdy któraś strona chce wysłać bajt:

   1. Wysyła przez jeden bit time stan 0 (bit startu).
   2. Wysyła bity danych (może ich być od 5 do 9, zazwyczaj jest ich 8).
   3. Być może wysyła bit parzystości.  W zależności od konfiguracji, jest to zawsze bit 0,
      zawsze bit 1, XOR wszystkich bitów danych (even parity), bądź zanegowany XOR (odd parity).
   4. Wysyła 1, 1.5, bądź 2 bity stopu (zawsze równe 1).
   5. Utrzymuje stan 1 aż do momentu, gdy chce wysłać kolejny bajt.

Komunikacja jest asynchroniczna -- odbiorca musi odtworzyć zegar przez zmierzenie, kiedy
zaczął się bit startu i odliczanie odpowiedniego czasu do kolejnych bitów.