.. _w03-sync:


==============================
Wykład 3: Układy synchroniczne
==============================

Data: 17.10.2018

.. toctree::

.. contents::


Układy kombinacyjne i sekwencyjne
=================================

Układ kombinacyjny to acykliczna sieć bramek logicznych.  W układzie
kombinacyjnym wartość sygnałów wyjściowych zależy tylko od wartości sygnałów
wejściowych (pomijając opóźnienia).

Układ sekwencyjny to sieć bramek logicznych zawierająca cykle.  Stany logiczne
na tych cyklach stanowią pamięć wewnętrzną układu, a żeby przewidzieć wartość
sygnałów wyjściowych musimy znać ich wartość (jak również wartośći sygnałów
wejściowych).

Wyróżniamy dwa typy układów sekwencyjnych:

- układy synchroniczne: zmiana stanu układu następuje tylko na skutek zmian
  jednego wyróżnionego sygnału wejściowego, zwanego wejściem zegarowym
- układy asynchroniczne: wszystkie inne


Zatrzaski
=========

Najprostszym układem sekwencyjnym (ale nie synchronicznym) są zatrzaski
(latch), czyli układy z 1-bitową pamięcią i sygnałem zapisu.

.. image:: dl.svg

Wyjście Q zawsze pokazuje stan zatrzasku.  Gdy wejście E ma stan 1, stan
zatrzasku jest ustawiany na wejście D, a gdy ma stan 0, stan zatrzasku się
nie zmienia.

Aby opisać w Verilogu logikę z zatrzaskami, wystarczy napisać proces podobny
do logiki kombinacyjnej, ale nie zawsze ustawiający swój rejestr wyjściowy::

    reg q;
    wire d, e;

    // RĂłwnowaĹźne pojedynczemu zatrzaskowi typu D.
    always @(d, e) begin
        if (e)
            q <= d;
    end

Pisząc logikę kombinacyjną za pomocą procesu, należy uważać, by zawsze
ustawiać stan wyjść -- w przeciwnym wypadku dostaniemy niechciany zatrzask.
W języku SystemVerilog ten problem został rozwiązany przez wprowadzenie słów
kluczowych ``always_comb`` (jak ``always``, ale powoduje błąd syntezy, gdy
powstały układ nie będzie kombinacyjny) oraz ``always_latch`` (powoduje błąd
syntezy, gdy powstały układ nie jest zatrzaskiem).

W zależności od technologii, zatrzask typu D może mieć więcej
dostępnych wejść.  Po szczegóły odsyłam do dokumentacji konkretnego układu
(bądź biblioteki komórek w przypadku ASICów).  Ten dostępny w Spartanie 3E
w pełnej konfiguracji (LDCPE) zachowuje się następująco::

    wire clr;      // Asynchronous clear/reset.
    wire pre;      // Asynchronous set/preset.
    wire d;        // Data input.
    wire g;        // Gate input (równoważne wejściu e powyżej).
    wire ge;       // Gate enable input.
    reg q = <...>; // Data output (możemy wybrać wartość początkową).

    always @(clr, pre, d, g, ge) begin
        if (clr)
            q <= 0;
        else if (pre)
            q <= 1;
        else if (g && ge)
            q <= d;
    end

Możliwość ustawienia wartości początkowej zatrzasków i przerzutników jest
cechą charakterystyczną układów FPGA i niektórych układów CPLD -- jeśli
projektujemy ASIC, stan początkowy jest nieustalony i musimy samemu zapewnić
odpowiedni układ resetujący stan przy włączeniu zasilania.

W logice, zatrzasków używa się rzadko -- szczególnie w przypadku FPGA, gdzie
zatrzaski i przerzutniki mają ten sam koszt.  Zatrzasków używa się natomiast
jako pamięć -- jeżeli używamy zewnętrznej asynchronicznej pamięci SRAM, jest
to po prostu wielka tablica zatrzasków i steruje się nią dość podobnie.


Przerzutniki
============

Najprostszym układem synchronicznym (i podstawowym elementem, z którego
składają się wszystkie układy synchroniczne) jest przerzutnik:

.. image:: dff.svg

Przerzutnik działa podobnie do zatrzasku, ale kopiuje stan wejścia D do
wyjścia Q tylko w momencie zmiany wejścia C z 0 na 1 -- gdy stan wejścia
będzie już ustabilizowany na 1, przerzutnik (w przeciwieństwie do zatrzasku)
nie będzie reagował na dalsze zmiany wejścia D.

W Verilogu możemy go opisać następująco::

    reg q;
    wire d, c;

    always @(posedge c) begin
        q <= d;
    end

Nigdy nie należy próbować samemu składać przerzutnika z bramek logicznych
w układzie FPGA (a w przypadku ASICa należy być bardzo, bardzo ostrożnym) --
jego poprawne działanie jest zależne od odpowiedniego ułożenia czasów opóźnień
propagacji na bramkach.

Przerzutniki, podobnie do zatrzasków, miewają więcej wejść.  W układzie
Spartan 3E mamy dostępne dwa rodzaje przerzutników: z synchronicznymi
i asynchronicznymi wejściami resetu.  Ten z synchronicznym resetem (FDRSE)
działa następująco::

    wire r;     // Synchronous reset input.
    wire s;     // Synchronous set input.
    wire ce;    // Clock enable input.
    wire c;     // Clock input.
    wire d;     // Data input.
    reg q = <...>;

    always @(posedge clk) begin
        if (r)
                q <= 0;
        else if (s)
                q <= 1;
        else if (ce)
                q <= d;
    end

Nie ma on większych możliwości od postawowego przerzutnika (poza wartością
początkową) -- dodatkowe wejścia dałoby się emulować logiką kombinacyjną.

Przerzutnik z asynchronicznym resetem (FDCPE) jest ciekawszy::


    wire clr;   // Asynchronous clear input.
    wire pre;   // Asynchronous set input.
    wire ce;    // Clock enable input.
    wire c;     // Clock input.
    wire d;     // Data input.
    reg q = <...>;

    always @(posedge clk, posedge clr, posedge pre) begin
        if (clr)
                q <= 0;
        else if (pre)
                q <= 1;
        else if (ce)
                q <= d;
    end

Działa on następująco:

- zawsze, gdy ustawiony jest asynchroniczny reset, stan jest ustawiany na 0
  (pozostałe wejścia, w tym wejście zegarowe, są ignorowane)
- zawsze, gdy ustawiony jest asynchroniczny preset, ale nie reset, stan jest
  ustawiany na 1
- jeśli żaden z asynchronicznych sygnałów nie jest ustawiony, ustawiony jest
  sygnał clock enable, i następuje zbocze rosnące na wejściu zegarowym,
  kopiuje stan wejścia d do stanu wewnętrznego
- w pozostałych momentach, utrzymuje poprzednią wartość stanu

.. warning::

  Asynchronicznego resetu nie da się emulować inną funkcjonalnością.  Przed
  użyciem bardziej skomplikowanych kombinacji, należy skonsultować się
  z dokumentacją używanego układu FPGA.  Czasem można spotkać następujące
  ograniczenia:

  1. Przerzutnik może mieć asynchroniczny reset albo asynchroniczny preset,
     ale nie oba naraz.
  2. Asynchroniczne wejście musi ustawiać przerzutnik na stan początkowy
     -- np. jeśli używamy asynchronicznego presetu, stanem początkowym musi
     być 1.


Układy synchroniczne, czasy setup i hold
========================================

Układ synchroniczny składa się z pewnej liczby przerzutników używających
wspólnego sygnału zegarowego i układów kombinacyjnych wyznaczająych nowy
stan układu.  Działa on następująco:

1. Przed rosnącym zboczem zegarowym stan układu i jego wejść jest stabilny.
2. Przychodzi rosnące zbocze zegarowe.
3. Każdy przerzutnik w układzie jednocześnie sampluje stan wejścia D
   i kopiuje je na wyjście Q.
4. Część kombinacyjna układu przez pewien czas liczy nowy stan.
5. Układ się stabilizuje -- wyjścia Q przerzutników wciąż trzymają stary
   stan układu, a na wejściach D jest już gotowy nowy stan układu.
6. Nadchodzi kolejne rosnące zbocze zegarowe.

Moment nadejścia opadającego zbocza zegarowego nie jest zbyt istotny -- liczy
się tylko rosnące zbocze.

Aby układ działał poprawnie, należy zapewnić, że wszystko stanie się
w odpowiednim czasie.  Każdy przerzutnik (lub inny gotowy układ synchroniczny)
ma następujące ważne parametry, których wartości można znaleźć w dokumentacji:

- Setup time (T_xS): wymagany przez przerzutnik czas, przez który stan wejścia
  D musi być stabilny przed rosnącym zboczem zegarowym, bo jego stan został
  poprawnie przeczytany.
- Hold time (T_xH): wymagany przez przerzutnik czas, przez który stan wejścia
  D musi być stabilny po rosnącym zboczu zegarowym, bo jego stan został
  poprawnie przeczytany.
- Clock-to-Output time (T_CKO): czas od rosnącego zbocza zegarowego do
  pojawienia się nowego stanu na wyjściu.

W zależności od konstrukcji, przerzutnik może mieć zerowy (bądź nawet ujemny)
czas setup albo hold, ale nie oba naraz.  PoniewaĹź czas hold jest
problematyczny (nie chcemy, by inny przerzutnik w układzie mógł popsuć układ
licząc nowy stan zbyt szybko), zazwyczaj dąży się do minimalizacji bądź
wyeliminowania go.  Co więcej, każda sensowna technologia ma ``T_CKO > T_xH``,
więc nie musimy się przejmować czasem hold dopóki nie mamy bardzo
nierównomiernych opóźnień w transmisji sygnału zegarowego i nie mieszamy
technologii (np. przez podłączenie innego układu do FPGA).

Pozostałe czasy determinują maksymalną możliwą częstotliwość sygnału
zegarowego, przy której układ synchroniczny działa poprawnie -- jest to
``1 / (T_CKO + T_xS + największe opóźnienie na logice kombinacyjnej
i przesyłaniu danych między wyjściem Q a wejściem D)``.

Analizę czasową wykonują za nas narzędzia -- jeśli nasz zegar mieści się
w podanej przez narzędzia maksymalnej częstotliwości naszego układu
(i spełnione są ograniczenia czasowe na synchroniczne sygnały wejścia/wyjścia),
wszystko jest w porządku.  W przeciwnym wypadku, musimy zwolnić zegar, bądź
wziąć się za przeprojektowanie układu.


Metastabilność
==============

Niestety, w prawdziwym świecie nie istnieją układy w pełni cyfrowe -- każdy
układ jest tak naprawdę układem analogowym i można wprowadzić go w stany
pośrednie (gdzieś pomiędzy 0 i 1).  Zapisując taki stan (szczególnie stan
bliski 0.5) do zatrzasku lub przerzutnika, możemy go wprowadzić w stan
rĂłwnowagi niestabilnej -- dowolne zaburzenie spowoduje, Ĺźe spadnie w stan
0 lub stan 1, lecz może to zająć dużo więcej czasu niż normalny parametr
T_CKO.  Takie zjawisko nazywa się metastabilnością.

Metastabilność jest zjawiskiem niebezpiecznym, gdyż dany stan może być
różnie zinterpretowany przez podłączone wejścia różnych bramek logicznych,
powodując w naszym układzie synchronicznym przejścia niespójne zarówno
ze stanem logicznym 0 jak i stanem logicznym 1.  Byłoby bardzo niedobrze,
gdyby np. różne części składowe procesora nie zgadzały się na temat tego,
czy w danym cyklu procesor rozpoczyna obsługę przerwania.

Metastabilność może powstać na dwa sposoby:

1. Dajemy układowi synchronicznemu stan pośredni jako wejście (należy
   tego nie robić).
2. Nie przestrzegamy czasu setup bądź czasu hold.

Metastabilność jest zjawiskiem nieuniknionym, gdy dany układ synchroniczny
ma wejścia asynchroniczne (a praktycznie każdy ma) -- prędzej czy później,
wejście zmieni się odpowiednio blisko zbocza sygnału zegarowego, powodując
naruszenie czasu setup bądź hold.  Musimy więc sobie jakoś z tym poradzić.

Z drugiej strony, stany metastabilne trwają bardzo krótko -- praktycznie zawsze
rozwiązują się w ciągu jednego, ewentualnie dwóch cykli zegara.  Z tego powodu,
na sygnałach asynchronicznych używa się tzw. synchronizatorów -- łańcuchów
dwĂłch lub trzech przerzutnikĂłw::

    wire async_in;
    reg tmp1, tmp2;
    reg out;

    always @(posedge clk)
        tmp1 <= async_in;
        tmp2 <= tmp1;
        out <= tmp2;
    end

Jeśli przerzutnik ``tmp1 <= async_in`` złapie wejście w momencie zmiany stanu
i wejdzie w stan metastabilny, z bardzo dużym prawdopodobieństwem stan ten
rozpadnie się do stanu 0 bądź 1 zanim jego wyjście zostanie użyte przez
kolejny przerzutnik (ma na to cały cykl zegara).  Jeśli nawet to się nie
uda, na przerzutniku ``tmp2 <= tmp1`` mamy kolejną szansę.  Na wyjściu
``out`` z bardzo dużym prawdopodobieństwem dostajemy już czyste wyjście
bez metastabilności.

Zapobieganie metastabilności ma charakter probabilistyczny, a odporność układu
na metastabilność podaje się jako MTBF (mean time between failures) --
szacowany średni czas bezawaryjnej pracy.  Jeśli osiągniemy MTBF rzędu setek
tysięcy lat, możemy go uważać za odporny na metastabilność.  MTBF zależy od:

- Częstotliwości zegara.  Większa częstotliwość to:

  - więcej szans, że coś pójdzie nie tak
  - krĂłtszy okres czasu w synchronizatorze na rozpad stanu metastabilnego

- Technologii wykonania (nowsze układy FPGA mają dedykowane bloki
  synchronizacyjne z przerzutnikami wykonanymi w specjalnej technologii
  zoptymalizowanej na zapobieganie metastabilności)

- Liczby przerzutników w synchronizatorze (MTBF rośnie wykładniczo z liczbą
  przerzutników).  Dwa przerzutniki to absolutne minimum, dla bezpieczeństwa
  zaleca się trzy.

Synchronizatorów należy używać *zawsze* na asynchronicznych wejściach.
W przypadku wejść z układów synchronicznych pracujących z inną częstotliwością
zegara, należy zapewnić jeszcze jeden przerzutnik na początku, używający sygnału
zegarowego układu źródłowego -- zapobiega to wydostawaniu się stanów pośrednich
z logiki kombinacyjnej.  Jedyny przypadek, w którym wolno pominąć synchronizator
na wejściu to wejście z innego układu synchronicznego pracującego na ściśle
związanym sygnale zegarowym (np. naszym sygnale zegarowym podzielonym bądź
pomnożonym przez stałą przez układ generowania zegara) -- w tym wypadku
należy dobrze opisać wzajemną relację tych zegarów, by narzędzia analizy
czasowej mogły sprawdzić zachowanie czasów setup i hold.

Zjawisko metastabilności występuje zawsze, gdy dwie konfliktujące zmiany
stanów mogą się zdarzyć blisko siebie -- w szczególności może też dotyczyć
asynchronicznych sygnałów reset, bądź wejścia do zatrzasku (gdy zmienia się
jednocześnie ze zmianą 1 na 0 na wejściu E).


O stanie początkowym i uruchamianiu FPGA
========================================

W układach FPGA mamy możliwość ustawienia stanów początkowych przerzutników,
więc sygnały reset do każdego przerzutnika nie są ściśle potrzebne
(w przeciwieństwie do ASICów).  Jeśli używamy sygnałów reset (by móc
przywrócić część układu do stanu początkowego), zazwyczaj łatwiej jest
użyć resetów synchronicznych.  Polecam tutaj lekturę:

https://www.xilinx.com/support/documentation/white_papers/wp272.pdf

Natomiast należy zauważyć, że jeśli nasz układ ma zacząć pracę od razu
po starcie (jak np. mikroprocesor, który natychmiast po włączeniu
zaczyna wykonywać kod z pamięci), samo zjawisko startu FPGA może
prowadzić do metastabilności jeśli stanie się to blisko stanu zbocza
zegarowego.  Rozwiązania tego problemu są dwa:

1. Zaprojektować stan początkowy układu tak, żeby był stabilny
   (następny stan == stan początkowy), z wyjątkiem jednego przerzutnika,
   stanowiącego początek synchronizatora, który spowoduje rozpoczęcie
   pracy układu ("iskry życia")::

       reg start0 = 0;
       reg start1 = 0;
       reg running = 0;

       always @(posedge clk) begin
           start0 <= 1;
           start1 <= start0;
           running <= start1;
       end

2. Zsynchronizować start układu FPGA do własnego zegara.


Procedura startowa FPGA
-----------------------

W przypadku układu Spartan 3E, procedura uruchamiania wygląda następująco:

1. Układ power-on reset monitoruje stan linii zasilających i utrzymuje cały
   układ w stanie początkowym dopóki nie osiągną one poziomu napięcia
   odpowiedniego do rozpoczęcia pracy.
2. Układ zaczyna generować zegar za pomocą wewnętrznego (bardzo niedokładnego)
   oscylatora.
3. Układ czyści pamięć konfiguracyjną.
4. Układ sprawdza stan linii sterujących wybierających źródło konfiguracji
   (na płytce Basys2 możemy wybrać dwie opcje za pomocą zworki).
5. W zależności od źródła konfiguracji:

   - jeśli układ ma sam wczytać konfigurację z kości flash, używa wewnętrznego
     oscylatora jako zegara konfiguracyjnego (CCLK) i rozpoczyna wczytywanie
   - jeśli układ ma być konfigurowany przez zewnętrzne źródło, wyłącza
     wewnętrzny oscylator i używa zewnętrznego zegara jako CCLK
   - jeśli układ ma być konfigurowany przez JTAG, układ wyłącza wewnętrzny
     oscylator i zatrzymuje się, czekając na instrukcje (i zegar) na porcie
     JTAG

6. Układ konfiguruje się, używając zewnętrznego bądź wewnętrznego sygnału
   CCLK bądź sygnału zegarowego z portu JTAG
7. Układ sprawdza poprawność konfiguracji.
8. Układ przechodzi z zegara konfiguracyjnego na zegar startowy, wybrany
   w konfiguracji (opcja ``StartupClk`` polecenia ``bitgen``).  Może to być:

   - ``JTAGClk``: zegar z portu JTAG.  Używając tego ustawienia z inną metodą
     konfiguracji niż JTAG, zawiesimy układ przed uruchomieniem.
   - ``CCLK``: ten sam zegar, co uĹźywany przy konfiguracji metodammi innymi
     niż JTAG.  Używając tego zegara z metodą JTAG, możemy zawiesić układ,
     jeśli nic nie podaje stosownego sygnału na odpowiednią nóżkę FPGA.
   - ``UserClk``: dowolny zegar wybrany przez użytkownika przez podłączenie
     go do odpowiedniego bloku ``STARTUP``.

9. Układ przechodzi przez kolejne fazy startu, w kolejności wybranej przez
   użytkownika (za pomocą opcji programu ``bitgen``).  Te fazy to:

   - włączenie sygnału DONE, oznajmiającego światu zewnętrznemu, że FPGA
     zakończyło konfigurację i rozpoczęło pracę.  W przypadku użycia wielu
     układów FPGA, mamy możliwość monitorowania sygnałów DONE z innych FPGA
     i poczekania, aż wszystkie będą gotowe (synchroniczny start układu
     złożonego z wielu FPGA).
   - wyłączenie specjalnego sygnału GTS (global three-state), blokującego
     wszystkie sygnały wyjściowe (zabezpiecza on przed nadawaniem
     nieustalonych sygnałów na wyprowadzeniach układu w trakcie konfiguracji).
   - oczekiwanie na uruchomienie i ustabilizowanie wybranych układów DCM
     (generatorĂłw zegara).
   - włączenie specjalnego sygnału GWE (global write enable), umożliwiającego
     zapis do pamięci zawartych w układzie (przerzutników oraz pamięci RAM) --
     do tego momentu, układ jest w stanie początkowym.

10. Układ wyłącza oscylator wewnętrzny, jesli nie zrobił tego wcześniej.


Prymityw STARTUP_SPARTAN3E
--------------------------

Możemy kontrolować pewne aspekty procedury startowej instancjonując prymityw
``STARTUP_SPARTAN3E`` (można to zrobić co najwyżej raz)::

        STARTUP_SPARTAN3E my_startup (
                // 4 wejścia, każde z nich opcjonalne.
                .CLK(clk),      // Zegar startowy (używany przez opcję UserClk)
                .GSR(gsr),      // Global Set/Reset
                .GTS(gts),      // Global Three-State
                .MBT(mbt)       // Multiboot trigger
        );

GSR jest globalnym sygnałem, który asynchronicznie resetuje wszystkie przerzutniki
w FPGA na ich stan początkowy (jest używany wewnętrznie w czasie konfiguracji) --
możemy go użyć do restartu naszego układu.  Nie jest to kompletny powrót do stanu
początkowego -- nie dotyczy on wewnętrznych pamięci RAM.

GTS został opisany powyżej -- można go użyć po uruchomieniu FPGA, by na chwilę
wyłączyć wszystkie wyjścia.

MBT powoduje pełny restart i rekonfigurację FPGA z alternatywnej konfiguracji
w równoległej pamięci flash.  Nie jest to dla nas zbyt interesujące, gdyż nasze
płytki nie mają takiej pamięci flash.

Używając wejścia CLK możemy zapewnić, że uruchomienie FPGA będzie zsynchronizowane
z naszym zegarem i uniknąć metastabilności przy starcie.