Hodinové domény

Oblast, které se nelze vyhnout, pokud má vaše zařízení komunikovat se světem.

Už minule jsem zmiňoval, že hodiny jsou svaté. Je to lehká nadsázka, ale rád bych teď probral podrobněji, co jsem tím myslel.

V odborné literatuře se o hodinových doménách můžete dočíst spoustu teorie. Například zde nebo zde, anglicky pak tady, tady nebo tady. Je to užitečné vědět a pokud to myslíte s návrhem obvodů vážně, musíte o této problematice vědět. Doporučuju tedy nastudovat, ale protože začátečníka může silně technický popis problému vyděsit, nabízím takový stručný úvod do problematiky.

Hodinové domény

Ve světě elektronických obvodů je potřeba synchronizovat činnosti. S kombinační logikou si člověk nevystačí, a pokud zavede asynchronní kombinační zpětnou vazbu do obvodu (viz například klopný obvod R-S), může se stát, že narazí na hazardní stavy, kdy se třeba obvod rozkmitá na frekvenci, omezené jen zpožděním signálů v logických členech. Proto se pro jakékoli složitější zapojení používá synchronizace pomocí hodinových pulsů. Hodiny mívají takovou frekvenci, aby zpoždění a doby náběhu, předstihy a přesahy jednotlivých komponent neměly vliv na funkci celého obvodu. Obvody se navrhují tak, že zpravidla reagují všechny na stejnou (např. náběžnou) hranu hodinového signálu. Ta pak udává „takt“, v jakém celé zapojení pracuje.

Ideální je, pokud si celé zapojení vystačí s jedněmi hodinami. Takhle například fungoval náš blikač. Někdy je potřeba mít víc frekvencí, což byl případ sériového vysílače v minulém článku. Tam ale naštěstí šla nižší frekvence odvodit od té vyšší, prostým podělením. Oba hodinové signály tak měly stejnou fázi (tj. náběžná hrana toho pomalejšího přicházela s náběžnou hranou toho rychlejšího).

Někdy to ale není možné. Někdy přichází signál, který má vlastní časování, vlastní takt. Jeho hodiny mohou mít stejnou frekvenci, ale pravděpodobně budou mít nejen jinou, ale i nesoudělnou. A i když budou mít frekvenci soudělnou (např. poloviční, třetinovou, desetinovou), tak se pravděpodobně bude lišit fáze (tj. náběžné hrany nepřicházejí ve stejný okamžik).

Problém nastává, když z obvodu s jedněmi hodinami přechází signál do obvodu s jinými. Vlivem různých hodinových frekvencí (analogie: samplovací frekvence) se může stát leccos. Krátké impulsy nemusí být zachyceny a mohou se ztratit. Změna hodnoty nemusí být zaznamenána. Impulsy se vlivem fázových rozdílů neúnosně zkrátí. A tak dál.

Další problém přináší metastabilita klopných obvodů. Pokud je klopný obvod citlivý např. na vzestupnou hranu hodinového signálu, je potřeba, aby datový vstup byl ustálený chvíli před příchodem této hrany (aby měl dostatečný předstih) a aby zůstal ustálený i chvíli po příchodu této hrany (aby měl dostatečný přesah). Zároveň je potřeba nechat obvodu určitý čas na zotavení po resetu. Všechny tyto časy jsou velmi krátké, ale v případě lehce fázově posunutých hodin se může signál právě do těchto bodů strefit. Pokud se tak stane, může se na výstupu klopného obvodu objevit nejednoznačný signál. Může přijít krátký parazitní zákmit, výstup se může rozkmitat, popřípadě být v neurčitém stavu…

Jak se vyhnout problémům?

Je několik způsobů, jak se vyhnout problémům. Úplně ten nejjednodušší je: Mít všude jen jeden základní hodinový signál.

Jenže to ne vždy jde. Jakmile připojíte obvod do „vnějšího světa“, začnou chodit různé asynchronní vstupy. Například sériový přenos po sběrnici SPI, ten si s sebou nese vlastní hodinový signál, nebo klávesnice s rozhraním PS/2, to jsou nejkřiklavější příklady rozdílných hodinových domén.

Je proto potřeba každý signál ošetřit. Ošetření se liší podle podstaty toho kterého signálu.

Průchod pomalých signálů

dlouhých impulsů je potřeba zajistit, aby prošly náběžné i sestupné hrany, které jsou synchronizovány se vstupními hodinami CLKin, a aby vznikly ekvivalentní náběžné a sestupné hrany, synchronizované s cílovými hodinami CLKout. Pokud CLKin < CLKout, nebývá to zas tak velký problém – cílový systém běží na vyšší frekvenci a je schopen hrany přebírat s minimálním zpožděním. U opačného případu se impulsy mohou prodloužit či zkrátit, a příliš krátké impulsy rychle za sebou nemusí projít.

Nejjednodušší by bylo připojit signálu do cesty klopný obvod D s hodinami připojenými na CLKout, tedy na hodiny cílového obvodu. Vzhledem k možné metastabilitě takového zapojení (signál se může změnit nebezpečně blízko hodinového pulsu) zapojujeme dva obvody za sebou. Ve VHDL pak zapisujeme například pomocí dvoubitového „posuvného registru“.

U pomalých signálů, což jsou v našem případě signály delší než trvání hodinového cyklu cílové oblasti, můžeme ignorovat zdrojové hodiny a používat jen ty cílové. V procesu máme dvoubitový signál „sync“, kde vstup dat jde do sync(0), sync(0) se posouvá do sync(1) a sync(1) tvoří výstup dat. Takto jsou vutvořeny dva klopné obvody D. Nevýhoda je, že se v signálu objeví zpoždění.

Pro testování synchronizace jsem si připravil testbench, ve kterém jsou tři různé hodinové signály: rychlý clk1 s periodou 10ns, pomalý clk2 s periodou 103ns (mírným „rozladěním“ proti celočíselnému násobku dosahuju fázového posunu) a opět rychlý clk3 s periodou 9ns (řádově stejně rychlý jako clk1, ale s drobným rozladěním, které vede k fázovým posunům).

timedomain-clocks

Na obrázku je v detailu vidět, jak se hodiny 1 a 3 posunou o 180° a jak hodiny 2 mají hranu úplně mimo.

Do série zapojuju dva synchronizační obvody. První je mezi clk1 a clk2, druhý mezi clk2 a clk3. Signál tak prochází z rychlé hodinové domény do pomalé, a z ní opět do rychlé. Generuju dva signály, slow a strobe, synchronizované s hodinami clk1.

Takto prochází obvodem signál slow (Din -> Dmid -> Dout):

timedomain-simple

A takto signál strobe:

timedomain-simple-strobe

Myslím, že nebudu přehánět, když napíšu, že máme vážný problém… Prošel zhruba každý patnáctý. Pro dlouhé signály je tento přístup použitelný, pro krátké rozhodně ne.

Průchod krátkých pulsů

Pokud potřebujeme korektně zpracovat krátké pulsy, musíme použít složitější způsob. Uvnitř synchronizačního obvodu si udržujeme informaci o tom, že „přišel puls“ (samozřejmě řízenou hodinovým kmitočtem zdroje), a pokud přišel, tak na výstupu vytvoříme puls podle hodin cílového obvodu. Například takto:

Takto napsaný obvod „propouští hrany“ s určitým zpožděním. Tentokrát je výsledek podstatně lepší:

timedomain-2clock

Vidíme, že pomalejší část poctivě tvoří pulsy podle krátkých pulsů na vstupu, s určitým zpožděním, a při konverzi z pomalých hodin na rychlé vznikají opět krátké pulsy.

Někdy může být výhodné, když dá převodník zdrojovému obvodu vědět, že puls ještě nedorazil do cílového obvodu. Princip spočívá ve spojení obou předchozích technik do dvousměrného synchronizačního obvodu.

timedomain-2clockbusy

Složitější informace

Pomocí těchto základních principů můžeme vytvářet složité synchronizační obvody, které dokážou přenášet několik signálů s různým významem, například informace o zahájení zpracování údajů, o konci zpracování, … Co když potřebujeme přenášet vícebitové hodnoty?

Pokud jde o prosté monotónní řady hodnot (čítání), pak zvažte převod do Grayova kódu. Grayův kód má tu výhodu, že při přechodu mezi hodnotami s krokem 1 se vždy mění pouze jeden jediný bit. To usnadňuje přechod mezi doménami, protože přichází vždy jen jedna hrana v jednu chvíli. Pokud bychom spoléhali na změnu více hran naráz, mohli bychom se opět ocitnout v hazardním stavu…

Pokud chceme přenášet obecně paralelní data, můžeme zvolit dva přístupy. Prvním je „převzorkování“ – na všechny bity použijeme výše uvedenou synchronizaci, a výsledek považujeme za směrodatný, i když počítáme s tím, že nám mohou některá data uniknout. Druhý přístup je přístup pomocí vyrovnávací paměti (bufferu), kam jedna doména zapisuje a druhá čte.

UART, druhý díl – přijímač

Minule jsem nakousl implementaci vysílače sériových dat ve tvaru 8-N-1. Dnes si ukážeme ideový protipól, tedy přijímač dat. Pokud jste se zamýšleli nad tím, jak takový přijímač implementovat, přišli jste na to, že problémem je synchronizace s přicházejícím signálem. Ten totiž ignoruje naše vnitřní hodiny a přijde si, kdy se mu zlíbí. U vysílače to až tak nevadilo – asynchronní puls na vstupu tx_send jsem si „pozdržel“ až do okamžiku, kdy přišel impuls na „vysílacích“ hodinách. V podstatě jsem také srovnával dvě hodinové domény…

U přijímače na to nemůžeme spoléhat. Tam přicházející sestupná hrana oznamuje stop bit, a je na přijímači, aby v tu chvíli spustil hodiny a podle této hrany přijímal data. Přístupy jsou různé. Někdo volí „oversampling“, tedy interní běh na vyšší harmonické frekvenci, např. 16x vyšší než je frekvence vysílání. Já jsem zvolil jiný přístup, kdy opravdu fyzicky držím hodiny vypnuté, s příchodem sestupné hrany je zapínám, a navíc první cyklus zkrátím o polovinu, abych se dostal vždy do středu předpokládaného intervalu a nevzorkoval v blízkosti hran. V ideálním případě tak čtu hodnotu vždy v polovině intervalu.

Do přijímače jsem implementoval i jednoduchý „low pass“ filtr pomocí posuvného registru. Krátké impulsy (rušení) tak nespustí případné přijímání dat. Filtr funguje tak, že do čtyřbitového posuvného registru vstupují vstupní data, a na výstupu je hodnota 0, pokud jsou všechny bity ve stavu „0000“, hodnota 1, pokud jsou všechny bity ve stavu „1111“, a pokud je kombinace jiná, tak se výstup nemění. Uvnitř zapojení už pracuju jen s tímto filtrovaným signálem.

Generátor hodin funguje podobně jako u vysílače, s tím rozdílem, že je řízen interním povolovacím signálem clken.

Detektor sestupné hrany pracuje tak, že si ukládá předchozí stav vstupu rx (filtrovaného) a porovnává ho s aktuálním stavem. Pokud je interní automat ve stavu „idle“, tak příchod sestupné hrany spustí hodiny (clken). Naopak návrat do idle a klid na lince znamená zastavení hodin.

Samotný přijímač je pak tvořen opět konečným automatem, který je obdobou toho z vysílače.

Přijímač posílá ven signál „rx_valid“, který oznamuje, že byla přečtena platná data. Nijak neřeší případné přetečení bufferu (taky neřeší, zda si už klient data vyzvedl), to je potřeba případně ošetřit v jiných částech.

Testbench

Jednoduchý testovací příklad, který spojuje vysílač a přijímač. Zvolil jsem mnohem vyšší frekvenci než 9600 Bd, to proto, aby nebylo potřeba tolik simulace.

uartrx

Pro fyzický test na kitu jsem použil jednoduchou aplikaci, která přijme znak, zneguje nejnižší bit a výsledek pošle zpět. Náš vysílač není „one shot“ – pokud po odvysílání zůstává „tx_send“ ve stavu 1, vysílají se stejná data znovu. Naopak přijímač drží rx_valid po celou dobu platnosti dat, až do příchodu dalšího start bitu. Proto nelze spojit rx_valid a tx_send napřímo (tedy lze, ale výsledek je trochu jiný, než byste čekali) a je potřeba zapojit tvarovací obvod, který převede vzestupnou hranu na impuls. (Což mi připomíná, že podobné obvody pro úpravu signálů by mohly být součástí nějakého pokračování.)