.. _w05-uart: =================================================== WykĹad 5: Komunikacja ze Ĺwiatem zewnÄtrznym â UART =================================================== Data: 17.11.2020 .. toctree:: .. contents:: SygnaĹy asynchroniczne ====================== ZaĹóşmy, Ĺźe chcemy w naszym ukĹadzie synchronicznym obserwowaÄ stan sygnaĹu zewnÄtrznego, ktĂłry nie jest zsynchronizowany z naszym zegarem. Okazuje siÄ, Ĺźe jest to problematyczne. DziaĹanie przerzutnikĂłw, metastabilnoĹÄ --------------------------------------- MowiliĹmy, Ĺźe przerzutnik zapisuje stan wejĹcia w momencie rosnÄ cego zbocza zegara. Jest to jednak duĹźe uproszczenie â w prawdziwym Ĺwiecie wykonanie idealnego przerzutnika jest niemoĹźliwe: 1. Prawdziwe przerzutniki nie obserwujÄ stanu wejĹcia w jednym punkcie czasu, tylko w pewnym przedziale, w ktĂłrym wejĹcie nie powinno siÄ zmieniaÄ. PrzedziaĹ ten zdefiniowany jest dwoma parametrami: - setup time: czas przed zboczem zegarowym od ktĂłrego wejĹcie powinno byÄ stabilne - hold time: czas po zboczu zegarowym do ktĂłrego wejĹcie powinno byÄ stabilne Aby przerzutnik poprawnie zapisaĹ stan wejĹcia w danym cyklu, wejĹcie nie moĹźe zmieniaÄ stanu w przedziale ``(zbocze - setup, zbocze + hold)``. ZdarzajÄ siÄ (doĹÄ czÄsto) przerzutniki o zerowym bÄ dĹş ujemnym czasie hold â pozwala to zagwarantowaÄ, Ĺźe dane wysyĹane synchronicznie z zegarem zostanÄ poprawnie zarejestrowane. MoĹźliwe jest teĹź wyprodukowanie przerzutnikĂłw z zerowym bÄ dĹş ujemnym czasem setup, choÄ rzadko siÄ to zdarza. Nie istniejÄ jednak (i nie mogÄ istnieÄ) przerzutniki, w ktĂłrych oba czasy sÄ zerowe bÄ dĹş ujemne. 2. Prawdziwe sygnaĹy danych nie zmieniajÄ stanu z 0 na 1 (i na odwrĂłt) natychmiast â w rzeczywistoĹci, kaĹźdy sygnaĹ jest analogowy i podczas tranzycji sygnaĹ przyjmie kaĹźdÄ wartoĹÄ poĹredniÄ . W przypadku naruszenia czasĂłw hold/setup, bÄ dĹş w przypadku podania na wejĹciu wartoĹci poĹredniej (pomiÄdzy 0 a 1), przerzutnik zapisze w sobie stan poĹredni, nie bÄdÄ cy ani zerem ani jedynkÄ . Taki stan nie jest stabilny â prÄdzej czy później nastÄ pi jego "rozpad" albo do 0 albo do 1. Potrafi jednak trwaÄ caĹkiem dĹugo (tym dĹuĹźej, im bliĹźej stanu 0.5 jest zapisany stan). Z tego powodu taki stan nazywa siÄ stanem metastabilnym, a zjawisko wystÄpowania takich stanĂłw nazywa siÄ metastabilnoĹciÄ . Okazuje siÄ, Ĺźe nie istnieje Ĺźaden limit czasu na istnienie stanu metastabilnego â moĹźe on teoretycznie trwaÄ dowolnie dĹugo. Zachowuje siÄ jednak jak rozpad promieniotwĂłrczy â moĹźna okreĹliÄ jego czas pĂłĹtrwania, a prawdopodobieĹstwo zostania w stanie metastabilnym spada wykĹadniczo z czasem (i bardzo szybko staje siÄ pomijalnie maĹe). ObsĹuga sygnaĹĂłw asynchronicznych w praktyce â wejĹcie ------------------------------------------------------ Aby poradziÄ sobie ze zjawiskiem metastabilnoĹci i nie pozwoliÄ na przedostanie siÄ stanĂłw metastabilnych do naszego ukĹadu, naleĹźy na kaĹźdym asynchronicznym wejĹciu zastosowaÄ tzw. synchronizator â ukĹad, ktĂłry "zatrzyma" stan wejĹciowy odpowiednio dĹugo, by ewentualne stany metastabilne zdÄ ĹźyĹy siÄ rozpaĹÄ. Synchronizator skĹada siÄ po prostu z kilku przerzutnikĂłw poĹÄ czonych szeregowo:: # UWAGA: nie uĹźywaÄ; w prawdziwym kodzie naleĹźy uĹźyÄ synchronizatora z biblioteki # Synchronizator z 3 przerzutnikĂłw. stage1 = Signal() stage2 = Signal() sync_input = Signal() m.d.sync += [ stage1.eq(async_input), # async_input jest sygnaĹem zewnÄtrznym stage2.eq(stage1), sync_input.eq(stage2), ] # W dalszej czÄĹci ukĹadu uĹźywamy sync_input. Idea dziaĹania synchronizatora jest prosta: stan jest trzymany w kaĹźdym przerzutniku przez caĹy okres zegara, co prawie na pewno wystarczy, by ewentualny stan metastabilny zdÄ ĹźyĹ siÄ rozpaĹÄ zanim dotrze do ostatniego przerzutnika (ktĂłrego wyjĹcie jest juĹź uĹźywane przez logikÄ synchronicznÄ ). Minimalny (i najczÄĹciej stosowany) rozmiar synchronizatora to 2 przerzutniki. Gdy potrzebujemy dodatkowej pewnoĹci, stosuje siÄ synchronizatory z 3-ma przerzutnikami. W praktyce to wystarcza, by prawdopodobieĹstwo wystÄ pienia metastabilnoĹci na wyjĹciu synchronizatora byĹo mniejsze niĹź inne powody potencjalnego bĹÄdnego dziaĹania ukĹadu (promieniowanie kosmiczne itp). W nMigen nie naleĹźy pisaÄ synchronizatora samemu â zamiast tego, uĹźywamy gotowej implementacji z biblioteki:: from nmigen.lib.cdc import FFSynchronizer # ... sync_input = Signal() m.submodules.my_input_synchronizer = FFSynchronizer(async_input, sync_input, # opcjonalne parametry i ich wartoĹci domyĹlne o_domain='sync', # moĹźemy zmieniÄ, gdy chcemy synchronizowaÄ do innej domeny niĹź sync reset=0, # wartoĹÄ poczÄ tkowa przerzutnikĂłw w synchronizatorze stages=2, # liczba przerzutnikĂłw w synchronizatorze ) DziÄki uĹźyciu tej wersji, nMigen automatycznie dostosuje wygenerowany kod do docelowej platformy, emitujÄ c odpowiednie atrybuty np. wyĹÄ czajÄ ce optymalizacjÄ dla tych przerzutnikĂłw (ktĂłra popsuĹaby dziaĹanie synchronizatora) czy wyĹÄ czajÄ ce wejĹcie pierwszego przerzutnika z analizy czasowej. NaleĹźy zauwaĹźyÄ, Ĺźe synchronizatory z natury opóźniajÄ sygnaĹ wejĹciowy o co najmniej dwa cykle â jest to gĹĂłwny powĂłd, dla ktĂłrego przechodzenie miÄdzy domenami zegarowymi wprowadza duĹźe opóźnienia. Niestety, nie da siÄ tego uniknÄ Ä w przypadku asynchronicznych zegarĂłw. ObsĹuga sygnaĹĂłw asynchronicznych w praktyce â wyjĹcie ------------------------------------------------------ Gdy wyprowadzamy z naszego ukĹadu wyjĹcie, ktĂłre bÄdzie obserwowane przez asynchroniczny ukĹad (pracujÄ cy w innej domenie zegarowej), rĂłwnieĹź naleĹźy uwaĹźaÄ. Tym razem jedynym problemem sÄ potencjalne glitche w kombinacyjnej czÄĹci naszego ukĹadu. Aby ich uniknÄ Ä, wystarczy zapewniÄ, Ĺźe kaĹźde asynchroniczne wyjĹcie naszego ukĹadu jest bezpoĹrednim wyjĹciem przerzutnika (czyli, w przypadku nMigen, nie jest przypisywane z domeny ``comb``). Port szeregowy, czyli UART ========================== RS-232, popularnie zwany portem szeregowym, jest jednym z najstarszych standardĂłw komunikacji, ktĂłre wciÄ Ĺź sÄ w uĹźyciu. Standard ten oryginalnie powstaĹ w roku 1960 w celu ĹÄ czenia dalekopisĂłw z komputerami mainframe za poĹrednictwem modemĂłw i sieci telefonicznej. Od tego czasu byĹ wielokrotnie przystosowywany do najróşniejszych celĂłw: - ĹÄ czenie komputerĂłw PC ze sobÄ (za poĹrednictwem modemĂłw bÄ dĹş bezpoĹrednio) - podĹÄ czenie do komputerĂłw myszy, drukarek, bÄ dĹş innych urzÄ dzeĹ peryferyjnych - podĹÄ czenie terminali do urzÄ dzeĹ bez wyjĹcia graficznego, jak serwery czy routery - podĹÄ czenie interfejsĂłw do debugowania i konfiguracji do rozmaitych urzÄ dzeĹ Oryginalny standard RS-232 wykorzystywaĹ zĹÄ cze DB-25 (później zamiast niego zazwyczaj DB-9) i transmitowaĹ dane cyfrowo uĹźywajÄ c napiÄÄ +12V i -12V. MiaĹ 8 sygnaĹĂłw danych: - RxD (receive data): dane szeregowe z modemu do komputera - TxD (transmit data): dane szeregowe z komputera do modemu - RTS (request to send): kontrola przepĹywu z komputera do modemu - CTS (clear to send): kontrola przepĹywu z modemu do komputera - DTR (data terminal ready): sygnaĹ gotowoĹci z komputera do modemu - DSR (data set ready): sygnaĹ gotowoĹci z modemu do komputera - DCD (data carrier detect): sygnaĹ gotowoĹci poĹÄ czenia telefonicznego, z modemu do komputera - RI (ring indicator): przychodzÄ ce poĹÄ czenie telefoniczne, z modemu do komputera Pierwsze dwa z tych sygnaĹĂłw transmitujÄ dane protokoĹem szeregowym, podczas gdy pozostaĹe sÄ prostymi stanami logicznymi. UkĹad ktĂłry potrafi odbieraÄ i wysyĹaÄ dane szeregowe w tym formacie nazywa siÄ UART (universal asynchronous receiver/transmitter). WiÄkszoĹÄ z tych sygnaĹĂłw jest niepotrzebna gdy uĹźywamy portu szeregowego do innych zastosowaĹ niĹź podĹÄ czenie modemu â w praktyce zazwyczaj uĹźywa siÄ tylko sygnaĹĂłw RxD i TxD, czasem dodajÄ c jeszcze RTS i CTS do kontroli przepĹywu. We wspĂłĹczesnych czasach, fizyczny port szeregowy (ze zĹÄ czem DB-9) w komputerach to rzadkoĹÄ â jeĹli takiego potrzebujemy, zazwyczaj musimy kupiÄ konwerter na USB, ktĂłre zazwyczaj sÄ zĹej lub bardzo zĹej jakoĹci. CzÄsto zdarzajÄ siÄ jednak ukĹady, ktĂłre majÄ interfejs logicznie taki sam jak RS-232, ale uĹźywajÄ cy innych poziomĂłw logicznych (zazwyczaj 0V i 5V lub 0V i 3.3V) i innych zĹÄ czy (bÄ dĹş bezpoĹrednio poĹÄ czone ze sobÄ na pĹytce). Na wielu pĹytkach z FPGA moĹźemy spotkaÄ ukĹad konwertujÄ cy z USB na UART (np. FTDI z rodziny FT232*), podĹÄ czony do FPGA interfejsem 3.3V, umoĹźliwiajÄ cy komunikacjÄ z komputerem. Na pĹytce uĹźywanej na zajÄciach (Pynq-Z2) mamy ukĹad FT2232HL, wystawiajÄ cy jednoczeĹnie interfejsy UART i JTAG przez USB. Ten UART jest jednak poĹÄ czony na pĹytce nie do FPGA, a do UARTa procesora ARM zawartego w Zynq. Na zajÄciach moĹźemy jednak uĹźywaÄ interfejsu szeregowego w FPGA przez podĹÄ Äzenie go do drugiego UARTa procesora ARM przez wewnÄtrzny interfejs EMIO, nawiÄ zujÄ c w ten sposĂłb komunikacjÄ miÄdzy procesorem ARM a naszym ukĹadem w FPGA. ProtokóŠszeregowy ------------------ RS-232 jest interfejsem asynchronicznym â nie zakĹada wspĂłlnego sygnaĹu zegarowego miÄdzy nadawcÄ a odbiorcÄ . ZakĹada jednak uzgodnione wczeĹniej parametry po obu stronach oraz w miarÄ (Âą5%) dopasowanÄ szybkoĹÄ transmisji i odbioru. Parametry protokoĹu szeregowego sÄ nastÄpujÄ ce: - szybkoĹÄ transmisji, w bitach na sekundÄ; najczÄĹciej uĹźywane wartoĹci to 9600 (czÄsto spotykany default), 57600 (maksymalna szybkoĹÄ jakÄ da siÄ przesĹaÄ przez liniÄ telefonicznÄ ), 115200 (maksymalna szybkoĹÄ na starych UARTach). - liczba bitĂłw danych w bajcie: spotyka siÄ od 5 do 9 bitĂłw, najczÄĹciej jest to 8 - typ bitu parzystoĹci: - N: none, brak (najczÄĹciej uĹźywany) - E: even, bit parzystoĹci ustawiony tak, by razem z bitami danych byĹo parzyĹcie wiele jedynek - O: odd, bit parzystoĹci ustawiony tak, by razem z bitami danych byĹo nieparzyĹcie wiele jedynek - M: mark, bit parzystoĹci zawsze rĂłwny 1 - S: space, bit parzystoĹci zawsze rĂłwny 0 - iloĹÄ bitĂłw stopu: 1, 1.5, bÄ dĹş 2 bity (najczÄĹciej jest to 1) ProtokóŠdziaĹa nastÄpujÄ co: - gdy nic nie jest przesyĹane, linia przesyĹu ma stan logiczny 1 - gdy nadawca chce przesĹaÄ bajt, wysyĹa nastÄpujÄ ce bity (przesyĹ kaĹźdego z nich trwa ``(1 / bity na sekundÄ)`` sekund): - 1 (jeden) bit startu: ma zawsze stan logiczny 0 - bity danych, od najniĹźszego bitu - bit parzystoĹci, jeĹli jest w uĹźyciu - bit lub bity stopu: majÄ zawsze stan logiczny 1 - po przesĹaniu bajtu, nadawca moĹźe natychmiast rozpoczÄ Ä transmisjÄ kolejnego bajtu, bÄ dĹş wrĂłciÄ do stanu nieaktywnego przez dalsze utrzymywanie linii w stanie logicznym 1 W tym protokole bit startu sĹuĹźy za synchronizacjÄ â gdy odbiorca zobaczy, Ĺźe wczeĹniej nieaktywna linia zmieniĹa stan logiczny z 1 na 0, rozpoczyna odbiĂłr danych i kalibruje swĂłj zegar odbiorczy tak, by kaĹźdy bit prĂłbkowaÄ w Ĺrodku przedziaĹu w ktĂłrym powinien byÄ przesĹany (``(idx + 0.5) * czas trwania bitu`` od poczÄ tku bitu startu). Bit (lub bity) stopu sĹuĹźÄ natomiast za padding miÄdzy bajtami (by odbiorca miaĹ czas wrĂłciÄ do stanu nieaktywnego i zauwaĹźyÄ kolejnÄ zmianÄ z 1 na 0 sygnalizujÄ cÄ kolejny bit startu). O ile implementacja nadawcy jest doĹÄ oczywista i nie zawiera wiele pola na twĂłrczoĹÄ, implementacja odbiorcy to bardziej ciekawa kwestia â moĹźna napisaÄ ukĹady odbiorcze róşniÄ ce siÄ funkcjami wykrywania i obsĹugi bĹÄdĂłw protokoĹu czy wykrywania niedopasowanych parametrĂłw transmisji.