.. _w06-clock:


==========================
Wykład 6: Sygnały zegarowe
==========================

Data: 24.11.2020, 01.12.2020

.. toctree::

.. contents::


O sygnałach zegarowych
======================

Sygnałem zegarowym nazywamy sygnał nadający tempo pracy układu.  W logice
synchronicznej jest to sygnał, którego zbocze powoduje rozpoczęcie jednego
cyklu pracy części układu (zwanego domeną zegarową) — przerzutniki
przechowujące stan układu otrzymują nowe wartości, synchroniczne porty odczytu
pamięci wykonują odczyt, portu zapisu wykonują zapis, po czym kombinacyjna
część układu rozpoczyna obliczenia których wyniki zostaną wykorzystane
przy następnym aktywnym zboczu zegara.

Zazwyczaj aktywnym zboczem zegara jest zbocze rosnące (tranzycja z 0 na 1),
choć równie dobrze może to być zbocze malejące (tranzycja z 1 na 0) —
układy FPGA zazwyczaj obsługują te opcje równoprawnie.  W niektórych układach
można spotkać przerzutniki, w których oba zbocza jednocześnie mogą być aktywne,
lecz jest to dość ezoteryczna rzadkość.

Zazwyczaj jako sygnału zegarowego używa się prostego sygnału okresowego
pochodzącego z oscylatora — zbocza zegara następują wtedy zawsze w stałych
odstępach.  Nie jest to jednak wymaganie — w przypadku "zwykłej" logiki
synchronicznej nic nie stoi na przeszkodzie, by sygnał zegarowy był dość
dowolny.  Możliwe jest zatrzymanie zegara na dowolnie długi czas, zmienny
odstęp pomiędzy zboczami, itp.  Jedyne wymagania, jakie musimy zachować to:

- minimalny okres zegara (czas pomiędzy kolejnymi aktywnymi zboczami) —
  wymagana minimalna wartość jest wyznaczana przez narzędzia P&R na podstawie
  opóźnienia najdłuższej ścieżki kombinacyjnej w naszym układzie (plus czasy
  setup i hold), a jej odwrotność jest maksymalną częstotliwością zegara
  jakiej możemy użyć

- minimalny czas wysoki i niski — gdy zegar zmienia stan z 0 na 1, musi
  pozostać w stanie 1 przez minimalny czas wysoki (podany w dokumentacji
  przerzutnika, RAMu, DSP, czy innych prymitywĂłw w naszej technologii);
  analogicznie działa minimalny czas niski; w praktyce jest to mało ważne
  ograniczenie o ile nasz sygnał zegarowy jest generowany w sensowny sposób
  (praktycznie na pewno najpierw będziemy mieć problem z minimalnym okresem
  zegara)

Możliwość zatrzymania zegara bez szkody dla układu jest bardzo przydatna
i często stosowana:

- w celu ograniczenia poboru prądu przez układ — zatrzymanie zegara kasuje
  całe dynamiczne zużycie prądu przez zatrzymaną część układu

- w celu debugowania układu (choć to jest bardzo skomplikowana sprawa)

Możliwe jest też użycie wyjścia układu synchronicznego (wyjścia przerzutnika)
jako sygnału zegarowego dla innego układu synchronicznego.  Rzadko jest to
jednak dobry pomysł (lepiej użyć specjalistycznego prymitywu do kontroli
zegara) — praktycznie jedynym przypadkiem, gdzie ma to sens są różnego rodzaju
wolne interfejsy wejścia / wyjścia: SPI, JTAG, I2C, itp.

Niektóre specjalistyczne układy (nazywa się je układami dynamicznymi) mają
ściślejsze wymagania co do sygnału zegarowego — wymagają stałego okresu, bądź
też po prostu nie pozwalają na zatrzymanie zegara dłużej niż przez określony
czas.  Przykładami są opisane dalej układy przekształcające sygnały zegarowe
bądź szybkie interfejsy wejścia/wyjścia.


Parametry sygnału okresowego
----------------------------

Jeśli nasz sygnał zegarowy jest zwykłym sygnałem okresowym (a nie czymś
generowanym ręcznie przez układ logiczny), opisujemy go za pomocą następujących
parametrĂłw:

1. Okres (period) — czas między kolejnymi zboczami rosnącymi, mierzony
   w ns, bądź częstotliwość zegara (odwrotność okresu, mierzona w MHz).
2. Wypełnienie (duty cycle) — część okresu zegara, przez który jego
   wartością jest 1, mierzone w %.  Zazwyczaj używa się zegarów o wypełnieniu
   50%, lecz zdarzają się inne wartości, szczególnie przy prostych dzielnikach
   częstotliwości.
3. Wahania (jitter) — miara niedoskonałości zegara, czyli tego, jak bardzo
   długość kolejnych okresów różni się od siebie; mierzone w ps lub w % okresu.
   Zdarza się rozróżniać wahania krótkoterminowe (różnica długości okresów
   w oknie czasowym kilku okresów) i długoterminowe (jak bardzo długość okresów
   różni się od siebie na przestrzeni sekund).  Musimy go odliczać od długości
   okresu przy analizie czasowej, by zapewnić poprawne działanie układu.
4. Faza (phase) — przesunięcie początku okresu w czasie.  To pojęcie ma sens
   tylko w przypadku grupy zsynchronizowanych zegarĂłw, gdzie mĂłwimy o ich
   wzajemnych relacjach.  Jest zazwyczaj mierzona w stopniach, rzadziej w ns.
   Jeśli mamy dwa sygnały zegarowe o tym samym okresie i fazach 0° i 90°,
   znaczy to, że drugi sygnał jest opóźniony o 1/4 okresu w stosunku
   do pierwszego.  Jeśli mamy sygnały o fazie 0° i 180° oraz wypełnieniu 50%,
   oznacza to, że drugi jest efektywnie zanegowaną wersją pierwszego.
   O fazie można też mówić w przypadku zegarów, których współczynnik
   częstotliwości jest prostym ułamkiem (np. 20MHz i 30MHz), lecz należy
   wtedy bardzo uważać na stosowane definicje.


Generatory sygnałów zegarowych
------------------------------

Okazuje się, że technologia w której wytwarzane są układy cyfrowe nie pozwala
na generowanie zegarów dobrej jakości — w każdym układzie mającym nietrywialne
wymagania co do zegara stosuje się zewnętrzny generator sygnału zegarowego.
Okazuje się jednak, że gdy już mamy sygnał zegarowy dobrej jakości, możemy
dość łatwo przekształcić go w dobry sygnał zegarowy o innych parametrach.

Istnieje wiele sposobów na wygenerowanie sygnału zegarowego.  Wspomnę tutaj
o trzech:

- oscylator pierścieniowy (ring oscillator)
- oscylator LC
- oscylator kwarcowy

Naprostszym typem oscylatora jest oscylator pierścieniowy — jest to po prostu
nieparzysta liczba bramek logicznych NOT połączona w pierścień.  Jego
częstotliwość to 1 / (suma opóźnień bramek i połączeń między nimi).  Jest
to bardzo niedokładny oscylator (rzędu ±30% różnicy długości okresu,
zależnie od temperatury, napięcia zasilania i różnic powstałych w procesie
fotolitografii) i nie powinien być stosowany w jakiejkolwiek sytuacji
wymagającej konkretnej częstotliwości pracy, ale bywa używany gdy potrzebny
jest po prostu *jakiś* zegar.  Przykładem oscylatora pierścieniowego jest
wewnętrzny zegar konfiguracyjny układu FPGA (jeśli został wybrany tryb
konfiguracji w którym to FPGA generuje sygnał zegarowy).

.. warning::

    Nie należy próbować tworzyć własnego oscylatora pierścieniowego używając
    programowalnej logiki — nie mamy wystarczającej kontroli nad ułożeniem
    i połączeniem układu, by sensownie kontrolować częstotliwość.

Trochę bardziej skomplikowanym generatorem jest generator LC, w którym używamy
rezonansu układu złóżonego z kondensatora i cewki do wygenerowania zegara.
Częstotliwością takiego zegara jest ``1/(tau * sqrt(L*C))``.  Wciąż, jest to
zegar nieco niedokładny (ma wahania rzędu ±1%) i nie może być używany w wielu
interfejsach wejścia/wyjścia (jeśli np. użyjemy go do wygenerowania sygnału VGA,
obraz będzie się dosłownie trząsł na monitorze).

Do generowania sensownie dokładnych (±0.001% wahań) zegarów w elektronice
używa się oscylatorów kwarcowych, które używają wibracji kryształu kwarcowego
do generowania sygnału zegarowego.  Dzięki elektronicznym układom przekształcającym
sygnały zegarowe (PLL), często wystarcza jeden kryształ (zazwyczaj o częstotliwości rzędu
10MHz-100MHz) do wygenerowania dowolnej liczby sygnałów zegarowych o dowolnych wartościach.


Dystrybucja sygnałów zegarowych
-------------------------------

W układach cyfrowych bardzo pożądane jest, by zbocza sygnału zegarowego
dochodziły jednocześnie do wszystkich przerzutników, którymi sterują.
Różnica w czasie przyjścia zbocza do różnych miejsc nazywa się clock skew
i powoduje szereg problemĂłw:

- jeśli suma clock skew i czasu hold jest większa niż czas propagacji
  między wyjściem jednego przerzutnika a wejściem drugiego,
  mamy naruszenie czasu hold i nasz układ nie będzie działać
- clock skew efektywnie dodaje się do wielu czasów propagacji, zmniejszając
  maksymalną możliwą częstotliwość zegara

Aby zminimalizować clock skew, w układach FPGA (i ASIC) istnieją specjalne sieci
dystrybucji sygnałów zegarowych, zaprojektowane tak, by długość ścieżki
od źródła sygnału do przerzutników była mniej-więcej stała.  W układach
Xilinx 7 Series mamy 32 bufory globalne, będące źródłami takich sieci.
Narzędzia do syntezy same wywnioskują, które z naszych sygnałów powinny
używać buforów globalnych, ale jeśli chcemy, możemy poprosić o to jawnie
przez zinstancjonowanie prymitywu ``BUFG``.  Dostępne jest również wiele
buforów regionalnych (obejmujących tylko część układu, za to z mniejszym
opóźnieniem), ale nie będziemy się nimi zajmować.

Co więcej, bufory globalne potrafią również pełnić rolę przełącznika między
dwoma róznymi źródłami zegara (prymityw ``BUFGMUX`` lub ``BUFGCTRL``) —
przydaje się to, gdy będziemy projektować układ, który powinien działać
na różnych częstotliwościach (tryb turbo, interfejsy sprzętowe
mające wolne/szybkie wersje, różne rozdzielczości VGA, itp).  Przełącznik
ten jest dość skomplikowany — bezpieczne przełączenie sygnału zegarowego między
dwoma źródłami wymaga dużej ostrożności, by nie naruszyć wymagań minimalnego czasu
niskiego/wysokiego i nie należy próbować tego robić ręcznie.

Specjalnym (i bardzo częstym) przypadkiem funkcjonalności przełączania zegara
jest możliwość wyłączenia go (czyli przełączania się między naszym źródłem zegara
a sygnałem stale równym zero) — taką funkcjonalność realizuje prymityw
``BUFGCE``::

    # Układ w domenie sterowanej przez clk_with_enable będzie synchroniczny
    # z domeną sterowaną przez clk_bypass, ale będzie wykonywał pracę tylko,
    # gdy sig_enable będzie prawdą — odbywa się to przez maskowanie sygnału
    # zegarowego.
    m.submodules.buf_a = Instance("BUFG",
        i_I=clk_orig,
        o_O=clk_bypass,
    )
    m.submodules.buf_b = Instance("BUFGCE",
        i_I=clk_orig,
        i_CE=sig_enable,
        o_O=clk_with_enable,
    )
    # Uwaga: aby te domeny były poprawnie zsynchronizowane, konieczne jest
    # użycie BUFG na clk_bypass, by zapewnić takie same opóźnienia dystrybucji
    # zegara.


Przetwarzanie sygnałów zegarowych
---------------------------------

Technologia produkcji układów cyfrowych nie pozwala na generowanie sygnałów
zegarowych dobrej jakości wewnątrz naszego układu.  Okazuje się jednak,
że mając już taki sygnał z zewnątrz (np. z oscylatora kwarcowego) można
wyprodukować układ przetwarzający go w sygnał zegarowy dobrej jakości o
innych parametrach.  Takim układem jest PLL (phase locked loop).

Istnieje bardzo wiele rodzajów układów PLL (występujących pod różnymi nazwami),
a ich użycie zawsze wymaga bezpośredniego użycia dość skomplikowanych prymitywów
zależnych od producenta i konkretnej technologii FPGA.  Takie układy mają jednak
dość podobny ogólny schemat działania:

1. PLL ma wejście zegarowe (nazwijmy je ``CLKIN``), do którego podłączamy
   otrzymany skądś bazowy sygnał zegarowy.
2. PLL zawiera oscylator o regulowalnej częstotliwości (zazwyczaj nazywany
   VCO — voltage controlled oscillator).  VCO generuje jakiś sygnał zegarowy
   (zazwyczaj dużej częstotliwości — w przypadku Xilinxa zakres to 800 – 1600MHz),
   który jest wejściem do kilku dzielników.
3. PLL zawiera kilka (2-8) programowalnych dzielników zegara, które produkują
   z wyjscia VCO nowe sygnały zegarowe, których częstotliwość to częstotliwość
   VCO podzielona przez jakąś niezbyt dużą stałą całkowitą (nazwijmy je
   ``DIV<idx>``).  Te dzielniki mają też zazwyczaj możliwość kontrolowania
   wypełnienia i relatywnej fazy wyjść.  Sygnały zegarowe generowane przez
   dzielniki są wyjściami PLLa (nazwijmy je ``CLKOUT<idx>``.
4. PLL ma drugie wejście zegarowe (nazwijmy je ``CLKFB``), do którego należy
   podłączyć wyjście ``CLKOUT0`` poprzez sieć dystrybucji zegara — tą samą
   (bądź wystarczająco podobną), co ewentualni użytkownicy sygnałów wyjściowych.
5. PLL zawiera układ porównywania fazy (phase comparator), który cały czas
   porównuje wejście ``CLKFB`` z wejściem ``CLKIN`` i tak steruje szybkością
   VCO, by wyrównać te wejścia w częstotliwości i w fazie.

   VCO początkowo generuje sygnał wyjściowy o kompletnie nieprzewidywalnych
   parametrach.  Układ porównywania fazy jednak stopniowo poprawia
   częstotliwość oraz fazę VCO tak, by ``CLKFB`` (czyli sygnał wygenerowany
   przez VCO podzielony przez ``DIV0``) stał się identyczny z ``CLKIN``.
   Gdy to nastąpi, PLL nazywa się zablokowanym (locked) — od tego momentu,
   układ porównywania fazy ciągle monitoruje te dwa sygnały i likwiduje
   najdrobniejsze odchylenia, a wygenerowany sygnał zgadza się co do cyklu
   z wejściem tak długo, jak długo wejście jest stabilne (nie zostanie
   wyłaczone i nie zmieni znacząco swojej częstotliwości).

6. PLL ma wyjście ``LOCKED``, które mówi czy PLL osiągnał już stan locked.

7. PLL ma wejście ``RESET``, które rozpoczyna od nowa procedurę dostosowywania
   VCO do wejścia ``CLKIN``.  Powinno się go użyć, gdy źródło sygnału wejściowego
   ulega zmianie.

Sygnał ``CLKOUT0`` jest de facto równy sygnałowi ``CLKIN`` przesuniętemu w fazie
do tyłu o tyle, ile wynosi opóźnienie dystrybucji zegara między ``CLKOUT0``
a ``CLKFB`` — pozwala to efektywnie zniwelować opóźnienie dystrybucji w naszym
układzie FPGA i sprawić, że zegar na wejściu naszych przerzutników będzie
wyrównany z zegarem na wejściu naszego całego układu, co przydaje się gdy
chcemy przesyłać dane synchronicznie z innymi układami na płytce drukowanej.

Znacznie ciekawszymi sygnałami są jednak pozostałe wyjścia ``CLKOUT<idx>`` —
zauważmy, że są wygenerowane z tego samego VCO co ``CLKOUT0`` przez proste
dzielniki zegara, a zatem są wyrównane do ``CLKIN`` z prostym współczynnikiem
częstotliwości.  Na przykład:

- ``CLKIN`` ma częstotliwość 50MHz
- ``DIV0`` wynosi 16
- ``DIV1`` wynosi 12
- VCO ustabilizuje się na częstotliwości ``CLKIN * DIV0``, czyli 800MHz
- ``CLKOUT0`` będzie miał częstotliwość ``VCO / DIV0``, czyli 50MHz (jak
  ``CLKIN`` przez pętlę sprzężenia zwrotnego)
- ``CLKOUT1`` będzie miał częstotliwość ``VCO / DIV1 (= CLKIN * DIV0 / DIV1)``, czyli 66MHz

Oznacza to, że nasz układ PLL efektywnie mnoży częstotliwość wejścia przez ``DIV0 / DIV1``
produkując wyjście ``CLKOUT1`` — za pomocą PLLi możemy więc uzyskać w miarę dowolne
częstotliwości mnożąc wejście przez odpowiednie ułamki (choć trzeba ostrożnie dobierać parametry tak,
by zmieścić się w wymaganiach PLLa).

W układach Xilinx 7 Series mamy dostępne dwa rodzaje układów PLL:

- prymityw ``MMCME2_BASE`` bądź ``MMCE2_ADV`` — trochę prostsza wersja
- prymityw ``PLLE2_BASE`` bądź ``PLLE2_ADV`` — ma więcej funkcjonalności

Wersje ``_ADV`` pozwalają na rekonfigurację parametrów w trakcie działania układu.
Po opis użycia tych układów odsyłam do dokumentacji:
https://www.xilinx.com/support/documentation/user_guides/ug472_7Series_Clocking.pdf


Domeny zegarowe w nMigen
========================

W nMigen sygnały zegarowe są w większości niejawne — są propagowane przez układ
w ramach obiektu typu ``ClockDomain``, reprezentującego domenę zegarową.
Domyślnie istnieje jedna domena zegarowa o nazwie ``sync``, ale możemy stworzyć
ich więcej.

Domena zegarowa (``ClockDomain``) to obiekt opakowujący następujące elementy:

- sygnał zegarowy: jednobitowy sygnał sterujący pracą tej domeny
- wybór aktywnego zbocza zegara (rosnące lub malejące; domyślnie rosnące)
- sygnał resetu: opcjonalny jednobitowy sygnał, którego ustawienie na 1 spowoduje
  ustawienie wszystkich rejestrów w domenie na wartość początkową
- typ sygnału resetu: synchroniczny (reset następuje na aktywnym zboczu zegara
  jeśli sygnał resetu jest ustawiony) bądź asynchroniczny (reset następuje gdy
  tylko sygnał resetu będzie ustawiony, niezależnie od zegara); domyślny (i zalecany)
  wybĂłr to reset synchroniczny

Możemy utworzyć nową domenę zegarową następująco::

    moja_domena = ClockDomain(
        # parametry i ich domyślne wartości:
        reset_less=False,       # jeśli True, domena nie będzie miała resetu
        clk_edge='pos',         # wybór aktywnego zbocza zegara — 'pos' oznacza rosnące, 'neg' oznacza malejące
        async_reset=False,      # jeśli True, reset jest asynchroniczny
        local=False,            # jeśli True, stworzona domena rozpropaguje się tylko do podmodułów; jeśli False, rozpropaguje się po całym układzie
    )
    m.domains += moja_domena

    # Podłączamy sygnał zegarowy, taki jak w oryginalnej domenie sync
    # Zamiast tego można by np. użyć PLL czy BUFGCE do użycia innego zegara.
    # Można też nie podłączać tutaj nic, by zegar domeny był wejściem układu
    m.d.comb += moja_domena.clk.eq(ClockSignal('sync'))

    # Podłączemy reset.
    m.d.comb += moja_domena.rst.eq(moj_reset)

    ctr = Signal(4)
    # To spowoduje wygenerowanie logiki w naszej nowej domenie.
    m.d.moja_domena += ctr.eq(ctr + 1)

Domeny zegarowe automatycznie propagują się w całym układzie — wystarczy ją stworzyć
w jednym module, by była widoczna wszędzie (chyba, że ustawimy jej parametr local).

Czasem chcemy użyć modułu "przenosząc" go do innej domeny zegarowej — powiedzmy,
że chcemy użyć jakiegoś gotowego modułu z biblioteki przystosowanego do pracy
w domyślnej domenie ``sync``, lecz chcemy by działał w naszej domenie.  Do takich
zastosowań możemy użyć konstrukcji ``DomainRenamer``::

    # Domena 'sync' w moj_podmodul i jego podmodułach jest tym samym co nasza domena
    # 'moja_domena' i kompletnie niezaleĹźna od naszej domeny 'sync'
    m.submodules.moj_podmodul = moj_podmodul = DomainRenamer({'sync': 'moja_domena'})(ModulZBiblioteki(...))


Komunikacja między domenami zegarowymi
======================================

Mając w układzie cyfrowym wiele domen zegarowych musimy w jakiś sposób
przesyłać dane między tymi domenami.  Poziom skomplikowania tego zależy
od tego, jak dużo danych mamy do przesłania, oraz od tego jaka jest wzajemna
relacja zegarĂłw w tych domenach.

Zdarza się, że dwie domeny są synchroniczne wględem siebie i możemy
deterministycznie po prostu używać w jednej domenie sygnałów wygenerowanych
w drugiej domenie.  Dzieje się tak, gdy:

- domeny mają ten sam zegar (różnią się tylko resetem)
- domeny mają ten sam sygnał zegarowy, ale przeciwne aktywne zbocza
- domeny mają różne sygnały zegarowe, ale pochodzące z jednego źródła
  z dobrze zdefiniowaną relacją fazy, np.

  - dwa wyjścia PLL o tej samej częstotliwości, ale fazie 0° i 90°
  - dwa wyjścia PLL, jedno o częstotliwości 100MHz, drugie o częstotliwości
    200MHz, wyrównane w fazie (każde rosnące zbocze wolnego zegara jest
    jednocześnie rosnącym zboczem szybkiego zegara)

- sygnały zegarowe w obu domenach są zmodyfikowanymi (np. przez ``BUFGCE``)
  wersjami tego samego sygnału bazowego

W przeciwnym wypadku domeny nazywamy asynchronicznymi względem siebie i musimy
bardzo uważać w komunikacji między nimi, by uniknąć problemu metastabilności.

W przypadku prostych sygnałów (np. linii przerwania) wystarcza synchronizator.
Problemy zaczynają się jednak, gdy mamy do przesłania bardziej skomplikowane
dane.


Kod Graya
---------

Załóżmy, że chcemy przekazać między dwiema domenami jakąś liczbę, która może
zmienić się co najwyżej o 1 (w górę bądź w dół) w kolejnych cyklach zegara.
Przekazanie jej bezpośrednio przez tablicę synchronizatorów nie zadziała
— przy zmianie liczby, zmiany różnych bitów mogą dojśc w różnych cyklach do nowej
domeny zegarowej.  Istnieje jednak kodowanie liczb, które rozwiązuje ten
problem — zapewnia, że każde kolejne dwie liczby są kodowane do wektorów bitowych
różniących się w dokładnie jednej pozycji.  Jest to kod Graya.  Dla przykładu,
kod 4-bitowy:

- 0: 0000
- 1: 0001
- 2: 0011
- 3: 0010
- 4: 0110
- 5: 0111
- 6: 0101
- 7: 0100
- 8: 1100
- 9: 1101
- 10: 1111
- 11: 1110
- 12: 1010
- 13: 1011
- 14: 1001
- 15: 1000

Aby zakodować liczbę ``x`` do kodu graya, wystarczy policzyć ``x ^ (x >> 1)``.
Dekodowanie jest trochę bardziej skomplikowane, ale dośc efektywnie realizowalne
w sprzęcie.


FIFO
----

Do przekazania dużej ilości danych między domenami zegarowymi najczęściej używa
się kolejek FIFO zrealizowanych za pomocą bloków RAMu używanych jako buforów cyklicznych:

- jeden port działa tylko w trybie zapisu w domenie źródłowej
- drugi port działa tylko w trybie odczytu w domenie docelowej
- wskaźniki odczytu i zapisu są przekazywane między domenami zegarowymi w kodzie Graya
  poprzez tablicę synchronizatorów