Proces

Tak, teď už to začíná trochu připomínat programování. Ale moc se neradujte, elektronické obvody se konstruují, nikoli programují!

Úmyslně jsem se tomu vyhýbal. V úvodu jsem psal, že máme tři možnosti, jak popsat obvod. Strukturní jsme si ukázali (to je to vytvoření instancí komponent a namapování vývodů na signály), i data flow (to je to, kde popisujeme, jak vzniká který signál). Chybí ta třetí… Ale ještě než se na ni podíváme, tak chci upozornit na jednu důležitou věc.

Představme si, že máme nějaký obvod, třeba AND-OR-INVERT – čtyřvstupový obvod, který provádí operaci Q = NOT((AB) + (CD)):

Máte to? A teď si představte, že ho zapíšu takto:

V čem je rozdíl?

Velmi správně: Není v tom rozdíl! U programování by v tom rozdíl byl, a pokud jste programátor, máte tendenci ho tam vidět: „Jak můžu přiřadit do Q něco, když u toho ještě neznám hodnotu?“ Špatně! Nic nikam nepřiřazuju a žádnou hodnotu neznám „už“ nebo „ještě“. Všechno se děje naráz, a výše uvedený zápis není „do AB zadej A and B… a pak do Q zadej…“ Ne. Čtěte to jako „Signál Q vznikne negací součtu signálů AB a CD. Signál AB vznikne součinem…“ atd. Což mimochodem zjednodušuje vytváření zpětných vazeb.

Pojďme zpátky k příkladu ze samotného úvodu – totiž ke klopnému obvodu R-S ze dvou hradel NOR.

Klopný obvod R-S, ilustrace CC-BY, autor Napalm Llama.

Teď už bychom měli vědět, jak ho popsat. Pojďme:

Strukturní popis bude vypadat nějak takto:

Vidíte, že strukturní popis není „čistý“, museli jsme použít interní signály sQ a snQ, to kvůli pravidlu „výstupní signál nemůže být připojen na vstup“. Komponentu NORGATE zde neřešíme, předpokládáme, že někde jinde máme toto

Dobrá. Jak vypadá data flow popis?

Behaviorální popis a proces

Behaviorální popis říká, jak už název napovídá, jak se obvod chová. Tedy nikoli jak vzniká signál Q a nQ, ale „Co se stane, když na vstup R přijde 1? A když přijde na vstup S?“ U R-S obvodu si to dokážeme představit, ale jsou situace, kdy dokážeme říct, co má obvod dělat, ale nechce se nám ho rozkládat na komponenty a popisy signálů. V dalších lekcích takových situací zažijeme ještě spousty.

Klíčovým pojmem behaviorálního popisu je proces. A teď dávejte prosím bedlivý pozor: Proces je výjimečný v tom, že se provádí sekvenčně! V procesu se postupuje odshora dolů a vykonávají se instrukce tak, jak jdou po sobě. Ale není to tak úplně jednoznačné, a vypečou vás hlavně signály.

Z programovacích jazyků můžete mít tendence řešit některé úlohy způsobem „nastavím A na hodnotu 0, pak něco udělám, pak nastavím A na hodnotu 1“. Pokud je A signál, tak leda houby, velebnosti! Proces se sice provede sekvenčně, ale pamatujte si, že hodnota se signálům nastaví „až pak, někdy na konci“. Syntetizátoru je úplně jedno, jak šíbujete s hodnotami, jako platnou vezme tu poslední přiřazenou. Na takovéhle operace se používají proměnné.

Proměnné jsou velmi podobné signálům, ale:

  • definují se lokálně v procesu a platí pouze pro daný proces
  • přiřazení není operátorem <=, ale operátorem :=
  • změna hodnoty je platná okamžitě až do další změny hodnoty

Proces si představme jako „event handler“ z jiných jazyků. Tedy krátkou sekvenci operací, které se provedou, když se něco stane. Každý proces má uveden tzv. sensitivity list, tedy seznam signálů, které si musí hlídat, a pokud se některý z nich změní, tak se proces vykoná. Tady je důležité uvědomit si, že ve skutečnosti v obvodu není žádná magická „programovatelná část“. Místo toho syntetizér vymyslí zapojení, které se chová tak, jako kdyby probíhal daný proces.

Obecný tvar procesu je:

Jméno je nepovinné, deklarace taky. Pokud používáme nějakou proměnnou (viz výše), deklarujeme ji zde. Klíčové slovo je variable.

Proměnné jsou lokální v daném procesu a jsou nevolatilní, to znamená, že mezi jednotlivými spuštěními procesu uchovávají svou hodnotu. Pokud počítáte s tím, že nějakou hodnotu má mít před prvním spuštěním procesu, použijte deklaraci s přiřazením.

Příkazy v procesu

V rámci procesu můžeme provádět (sekvenčně) příkazy. Kromě přiřazení jsou to i jiné konstrukce, známé z programovacích jazyků.

Přiřazovací příkaz

Jen pro pořádek. Můžeme přiřadit hodnotu signálu pomocí <=, nebo proměnné pomocí :=

Podmíněný příkaz

Příkaz case

Ekvivalentní příkazu „case“ z Pascalu, popř. konstrukci switch-case z C-like jazyků, ovšem s tím rozdílem, že se provedou jen ty příkazy, které jsou u dané hodnoty, není tedy třeba „break“. Může připomínat SELECT, který jsme si popisovali jako jeden z možných tvarů přiřazení. Hlavní rozdíl je v tom, že SELECT je výraz, CASE příkaz. Select se používá u přiřazení, CASE v procesu.

Příkaz wait

Tento příkaz má tři různé formy. V návrhu obvodu použijete jen první dvě, tu třetí využijete v simulačním testbenchi.

První je wait until (podmínka). Tento příkaz můžeme použít pouze v procesu, který nemá sensitivity list (tj. jakoby běžel neustále) a říkáme jím, že se má provádění pozdržet až do chvíle, než bude splněná podmínka.

Druhý tvar je wait on {signál}. Opět můžeme použít pouze v procesu bez sensitivity listu. Pozdrží provádění do změny signálu.

Třetí tvar je wait for {čas}. V testovacím obvodu pro zapojení jím můžeme předepsat čekání na určitou dobu. Vhodné například pro generování hodinových pulsů:

Příkaz loop

Ano, i ve VHDL existují smyčky.

Uvnitř smyčky můžeme použít slovo exit (ekvivalent „break“, tedy vyskočení ze smyčky), buď v podmíněné větvi nebo třeba ve tvaru exit when (podmínka). Obdobou příkazu „continue“ je příkaz next – tedy další iterace. Opět možno zapsat jako next when (…)

Příklad procesu

Vraťme se ještě k našemu klopnému obvodu R-S. Jak ho popsat behaviorálně? Já zvolil tento způsob:

Tedy: architekturu tvoří jeden proces, který hlídá vstupy R a S a spustí se ve chvíli, kdy se jejich hodnoty změní. V procesu jsem si nadefinoval proměnnou state – to je pro mne „interní stav klopného obvodu“, tedy proměnná, kde mám uloženou zapamatovanou hodnotu. Tato proměnná se propisuje do signálů Q a nQ na konci procesu. Tady je umístění významné! Kdybych dal tyto dva řádky na začátek procesu, nastavila by se hodnota signálů podle původního stavu proměnné!

Ve třech podmínkách vyhodnocuju, co se stalo a jak zareagovat. Buď je nastavený signál R, a pak je interní stav 0, nebo je nastavený signál S, a pak je interní stav 1, nebo jsou nastavené oba vstupy, a pak je interní stav nedefinovaný (X). Pokud jsou oba signály R i S nulové, nijak to neřeším a stav se nemění.

Pomocí tohoto zápisu mohu nadefinovat např. to, že vstup R bude prioritní, tedy když přijdou signály R i S, bude mít „navrch“ signál R a vnitřní stav bude 0…

Na konci článku naleznete testbench, který vyzkouší všechny čtyři architektury a…

Moment, říkal někdo čtyři?

Ano, mám připravenou ještě čtvrtou architekturu, která je opět behaviorální, ale v níž jsem místo proměnné použil signál, abych názorně předvedl rozdíl mezi proměnnou a signálem.

Na první pohled není vidět rozdíl, jen místo proměnné je použitý signál. Tipnete si, co se stane?

Testbench je zde:

Na chvilku se u něj zastavím. Všimněte si, že používám čtyři instance entity rsko. Liší se od sebe použitou architekturou. Odkazuju se „plným jménem“, tedy „work.rsko“ (work je jméno knihovny – aktuální projekt je vždy work, rsko je jméno entity) a explicitně říkám, že jde o entitu. Tedy entity work.xxx(architektura). A navíc nikde neuvádím deklaraci komponenty…

Tip: Pokud se vám zajídá kopírování port() a generic() z entity do komponenty a říkáte si, že to je hloupost, navíc máte kód zapráskaný duplicitními zápisy a tak dál, tak vám může tenhle způsob pomoci. Komponentu neinstancujeme jejím názvem, ale zápisem „entity library.name(architecture)“ – pokud se jedná o entitu z téhož projektu, tak použijte speciální jméno knihovny „work“. Uvedení architektury je nepovinné. Můžete taky přesunout entity do knihovny – viz další tipy.

Každé instanci připojím stejné vstupy, výstupy připojuju na Qx a nQx.

simrs

Vidíme, že signály začínají na 0, pak přijde puls na S, pak na R, pak opět na S, opět na R, a nakonec na obou najednou.

Dataflow architektura (Q1, nQ1) začne správně na stavech X, pak správně reaguje na signály, a na konci, když jsou oba vstupy aktivní, nastaví oba výstupy do log. 0. Je to logické chování, otázka je, nakolik nám takové chování v obvodu vadí.

Strukturní architektura (Q3, nQ3) se chová naprosto stejně.

Behaviorální architektura (Q2, nQ2) se chová správně: korektně zareaguje na nedovolený stav R=S=1 a nastaví správně oba signály na X.

Behaviorální architektura se signálem (Q4, nQ4) se chová velmi podivně… Jako by se změny děly správně, ale pozdě! Proč?

Když se nad tím zamyslíte: problém se skrývá v tom, co jsme si tu už řekli jen tak mimochodem o rozdílu mezi proměnnou a signálem: Signál se nastaví „někdy potom“ podle posledního známého přiřazení, zatímco proměnná ihned. Takže když provedu

tak se proměnné state přiřadí hodnota ‚0‘ OKAMŽITĚ, tedy v tu chvíli, kdy je přiřazena, a další příkaz už pracuje s touto hodnotou. Naproti tomu

znamená, že se NA KONCI PROCESU přiřadí do Q to, co je během procesu ve state, a do state ‚0‘. Jinými slovy do Q přiřazujeme tu hodnotu state, co měla na začátku procesu. Chování tomu odpovídá: Přijde signál S, zavolá se proces, a na jeho konci se nastaví Q podle úvodní hodnoty state (‚X‘) a state na ‚1‘. Výstup je tedy stále ‚X‘. Za chvíli nato přijde sestupná hrana S, tedy další změna. Opět se vyvolá proces, a na jeho konci se nastaví Q podle úvodní hodnoty state (‚1‘) – state samotné se nemění. A tak dál. Změny se tedy propisují o jedno volání později.

Tohle je další věc, kterou si musíte uvědomovat neustále: V procesu se jako hodnota signálu bere ta, která byla na začátku. Po celou dobu běhu procesu je stejná. Všechny změny jako by se zapisovaly do pracovního registru, a do samotného signálu se propíšou až na konci procesu (tedy ta hodnota, kterou přiřadíme jako poslední).

Tip: Co by se stalo, kdybychom u toho posledního behaviorálního modelu, tj. se signálem, umístili ty dva řádky s přiřazením výstupních signálů mimo proces?

Vylepšená generická sčítačka

Já vím, už byste chtěli blikat tou LEDkou, a já tu furt se sčítačkou. Ale tohle je docela dobrý trik…

Vzpomínáte na „generickou sčítačku„, které jsme jen zadali počet bitů, a ona se vytvořila přesně podle požadavků? Pojďme ji přepsat tak, že v ní použijeme procesy i aritmetiku.

Používám jeden proces, citlivý na signály A, B a Cin. Uvnitř mám definované dvě pomocné proměnné. V té první sčítám (a zvýším o 1, pokud je přenos), tu druhou použiju pro konverzi na vektor a rozebrání zpět na signály. U sčítání udělám ten trik, co zvýší šířku výsledku o 1 bit. Zbytek je, myslím, naprosto přímočarý a nepotřebuje vysvětlování.

Gratuluji, tímto máme za sebou naprosto nezbytné základy, a od příště už budeme jen probírat praktická zapojení… A možná si i blikneme!