Hodiny

Konečně si blikneme!

Tak, nadešel ten okamžik, kdy nám FPGA blikne.

Hello world!

Máme kit, na něm LED, kde je problém? Budeme blikat v sekundových intervalech, už víme jak se dělá proces, takže normálka, ne… LEDku nahodit, počkat sekundu, LEDku vypnout…

Moment, jak jako počkat sekundu?

No, minule jsi přeci psal… „wait for 1s;“ – nojo, to jsem psal, ale taky jsem psal, že to je pouze pro simulaci!

Aha, nojo, tak prostě… něco… třeba jako invertor, k němu kondenzátor… – Nemáme. Teda invertorů máme kýble, ale nemáme ten kondenzátor.

A co kdyby se dalo těch invertorů hodně za sebe, tak by to zpoždění vygenerovalo nějaké impulsy a… – a právě cváláte po dráze, postavené z hazardních stavů. Nene, nic z toho nepůjde. To, co potřebujeme, je úplně prostý generátor hodin, a musí být někde venku!

Naštěstí na většině kitů je. Na tom mém je taky, kmitá na frekvenci 50 MHz a je připojený na pin 17.

Hodiny máme. Blikání tedy bude jednoduché, stačí podělit ten kmitočet konstantou 50.000.000, neboli padesát milionů, a máme to.

Jak dělit? Tak v zásadě bude potřeba nejdřív někde ty impulsy počítat. Jakmile přijde vzestupná hrana hodin, tak k počítadlu přihodím 1. No a když se to dostane na 50 mega, tak si počítadlo zase vynuluju a změním stav LEDky.

Entita je jasná: Jeden vstupní signál s hodinama, jeden výstupní pro LEDku. Architekturu tvořím behaviorální, v ní se dobře reaguje na změny signálů, a to je přesně to, co potřebuju dělat s těmi hodinami. Takže si vytvořím proces, který bude reagovat na změnu hodin. Použiju dvě proměnné, jednu typu integer, kde si budu udržovat počet cyklů, a pak druhou, kde budu mít stav LEDky. V procesu se nejdřív podívám, jestli přišla vzestupná hrana hodin – k tomu slouží funkce rising_edge(clk). Pokud ano, zvyšuju počítadlo o 1. Pokud dosáhlo hodnoty 50 milionů, tak počítadlo vynuluju a invertuju hodnotu v proměnné „blik“, což je interní stav LEDky. A nakonec, ať se děje co se děje, pošlu tuhle hodnotu na výstup led.

Proč jsem použil proměnnou blink? Nemohl jsem zde použít signál? Mohl, ale platilo by to, co jsem psal minule: změna by se projevila až při další změně hodin. Tady by to asi moc nevadilo, ale radši to nedělám, protože to prostě není dobré. Proč jsem nepoužil rovnou led <= not led? Protože „led“ je výstupní signál, a ten nemůžu použít ve výrazu. (Ve verzi VHDL2008 to už jde, ale právě tahle featura nepatří mezi ty, které jsou podporované v mojí verzi Quartus II.)

Existuje alternativa k rising_edge, a zmínil jsem ji v pasáži o atributechif (clk’event and clk=’1′) Tedy pokud došlo k události na signálu CLK, a zároveň je teď signál CLK = ‚1‘… pak ta událost logicky musela být vzestupná hrana…

Jestli se rozhodnete tenhle kód opravdu vyzkoušet, nastavte si – pokud máte stejný kit – signál LED na pin 7 a signál CLK na pin 17.

Alternativní blikání

Napadla mě ještě alternativa, při které si architekturu rozdělím na dvě části, na asymetrickou děličku 1:50000000 (má nestejné délky pulsů), a na děličku 1:2. Z první půjde signál Hz1, na který bude navěšena druhá. Ta bude používat jednobitový signál ff (jako že flip-flop), který při každé náběžné hraně zneguje. Mimo tento proces si napojím výstup LED na signál ff. (Za domácí úkol si zkuste odvodit, proč tady mít signál nevadí, a v předchozím by to vadilo).

V architektuře jsou dva procesy, jeden reaguje na změnu clk, druhý na změnu Hz1.

Poznámka: Když jsem to psal poprvé, udělal jsem chybu. V prvním procesu jsem nastavoval defaultní hodnotu Hz1 na ‚0‘ mimo podmínku s clk. Nějak takto:

 

Syntetizér to odmítl přeložit s tím, že signál Hz1 nijak neudržuje svoji hodnotu mimo to zpracování hodin. Což je trochu kryptické sdělení a netušil jsem, co po mně chtějí. Odpověď je: Pokud máte podmínku, která je závislá na události nějakého signálu (typicky náběžné a sestupné hrany časového signálu), dbejte, aby signály měly přiřazenou hodnotu ve všech větvích vnořeného if. Každopádně jsem díky tomu vytvořil další alternativní strukturu, která nemění signál Hz1 v podmínce, závislé na clk:

S ní už problém nebyl.

Ještě alternativnější blikání

Ono se čísly, které v desítkové soustavě vypadají dobře (třeba „50000000“) v logických obvodech špatně dělí. To spíš 2, 4, 8, 65536 nebo 33554432 (což je 225, kdybyste to chtěli spočítat). Pokud nechceme přesně sekundu, ale „plus mínus něco tak aby to bylo okem vidět“, tak použijte prostý binární čítač, kde budete načítat pulsy hodin, no a LEDka bude jeho 24. bit, například.

 

Klopné obvody, registry a další…

Když už jsem to minule načal a teď jsme si tu zavedli koncept časového signálu, pojďme se podívat na některé základní klopné obvody.

Klopný obvod D

Tento klopný obvod má dva vstupy, D a C, a výstup Q. Když přijde vzestupná hrana signálu C (Clock), zkopíruje se na výstup Q hodnota vstupu D (Data). V ostatních případech si výstup Q udržuje předcházející stav bez ohledu na vstupy.

A takhle to je, doslova, nic víc není potřeba.

Klopný obvod D s asynchronním nulováním a nastavením

Tento obvod odpovídá funkcí předchozímu. Navíc má vstupy R a S. Pokud přivedeme 1 na vstup R, zapíše se hodnota ‚0‘, pokud na vstup S, zapíše se hodnota ‚1‘. Prioritu má vstup R. Vstupy R a S jsou asynchronní, tzn. změna se projeví hned a nečeká se na hodiny.

Stejný vzor můžete použít všude, kde se míchají asynchronní a synchronní vstupy. Nejprve v podmínkách ošetříte asynchronní, a nakonec si jednu podmínkovou větev vyhradíte pro synchronní operace.

Pokud chcete drsnější, „hackerštější“ podobu, co třeba takto?

Osmibitový registr

Nebude to o moc složitější než registr D. Ve skutečnosti jsou tyto registry vlastně jen vícenásobné registry D. Ve VHDL to díky vektorům zapíšeme úplně stejně. Pojďme si ale ukázat, jak implementuju funkci „povolovacího vstupu“, tj. hodnota se zapíše jen tehdy, pokud je vstup E (Enable) v log. 1.

Přibyla jedna podmínka. Všimněte si, že proces není citlivý na signál E – a je to v pořádku. Jediné, co změní stav obvodu, je signál C, a proto je proces závislý pouze na tomto signálu. E je pomocný signál, a jeho změna se na stavu obvodu nijak neprojeví.

Osmibitový posuvný registr se synchronním vstupem

Posuvné registry slouží k serializaci a deserializaci dat. Tento typ se synchronním vstupem můžeme použít k serializaci osmibitového čísla. Což se může hodit například při implementaci sériového rozhraní. Posuvný registr má bitový vstup Din, bitový výstup Dout, vstup hodin C, osmibitovou vstupní bránu D a řídicí vstup Load. Uvnitř je osmibitový registr. Funkce je taková, že při každém hodinovém pulsu se posunou bity o jednu pozici doleva, nejvyšší bit je „vytlačen“ na výstup Dout a do nejnižšího bitu je zkopírována hodnota Din. Pokud je při náběžné hraně signál Load = ‚1‘, tak se do registru zkopíruje obsah na datových vstupech D.

Pro zajímavost si zkuste nasimulovat testbench pro tento registr:

V testbenchi jsem použil proces na generování hodin, který využívá konstrukci wait for.

Dekodér 1 z 8

Též znám jako 3205… Tedy ne úplně, vynecháme povolovací vstupy. Namísto procesu použijeme čistě kombinační přístup data flow, v podstatě pravdivostní tabulku:

Multiplexer 4 na 1

Obsahuje dvoubitový řídicí vstup Sel, čtyři vstupy A, B, C, D a výstup Q. A udělejme si ho třeba generický, s libovolnou šířkou datové sběrnice!

ALU

neboli Aritmeticko – Logická Jednotka, ve starší české litratuře tedy ALJ. Ne, nebojte se, nebudeme si ukazovat, jak sestavit aritmetickou jednotku, to až někdy příště. Tentokrát jen taková zajímavost…

Představte si, že máte obvod, kde je osm vstupů A, B, C, D, E, F, G a H a dvoubitový řídicí vstup Sel. Podle hodnoty Sel je na výstupu součet dvou vstupních signálů, a to takto:

Sel Výstup
00 A+B
01 C+D
10 E+F
11 G+H

Podle této specifikace je zapojení takové, že máme čtyři sčítačky (A+B, C+D, E+F, G+H) a multiplexer, který vybírá jeden z výsledků. Ovšem to není rozhodně optimální řešení (4 sčítačky, 1 multiplexer). Optimální řešení je použít dva multiplexery ((A, C, E, G) a (B, D, F, H)) a jejich výstup poslat do jediné sčítačky, která je tak sdílená pro všechny čtyři operace.

Obecně je dobrý zvyk co nejvíc sdílet právě aritmetické operace, porovnávání a podobné náročnější věci. Velmi snadno se stane, že v kódu napíšeme velmi podobná sčítání do různých větví podmíněných příkazů (například ve stavovém automatu). Řešení je oddělit procesy, vyhradit si jeden speciální pro náročné „sdílené zdroje“…

Tip na tři „quick references“ PDFka: VHDL Types and Operators, VHDL Quick Ref, 1164 Packages. Plus jedna reference v HTML: VHDL knihovny.

Příště: Funkce!