Komponenty a signály

V předchozí části jsme už na obojí narazili. Pojďme si nyní tyto pojmy probrat podrobněji. Doufám, že jste část věnovanou testování nepřeskočili. To by byla velká chyba. Ukázali jsme si v ní totiž další dva základní koncepty.

Komponenty

První z nich je koncept komponenty. Elektronické obvody se skládají z celků, které se skládají z menších celků… atd. Například nějaká deska obsahuje několik multiplexerů. Multiplexery jsou složené z hradel, hradla jsou složena z tranzistorů… I ve VHDL je postup, jak nadefinovaný jednodušší obvod použít ve složitějším. Už jsme si ukázali, jak se obvod popisuje, že se skládá z deklarace entity a z popisu architektury. To jsou pro nás stavební bloky, které můžeme použít v jiných stavebních blocích. Podobně jako v jazyce C v jednom souboru funkci deklarujeme (.h), v jiném definujeme (.c) a v dalším používáme (.c), tak i ve VHDL musíme v té části, kde entitu použijeme, zopakovat její deklaraci, aby syntetizér věděl, jak entita komunikuje s okolím (o její architektuře nepotřebuje vědět nic). Použije se k tomu postup, při němž zopakujeme deklaraci entity, jen místo „entity“ napíšeme „component“. Tím se z „entity“ stává „komponenta“ pro daný obvod. Entity se zapisují do architektury, mezi hlavičku („architecture X of Y is…“) a začátek definice („begin“). Komponentu pak můžeme použít, vytvořit její „instanci“. Při tomto „instancování“ musíme říct, kam se připojí jednotlivé vstupy a výstupy dané komponenty. Slouží k tomu klíčová slova „port map“ – tedy „mapování portu“. Port je deklarován v entitě, jeho deklarace je zopakována v komponentě, a za slovy „port map“ je v závorce uvedeno, kam se připojuje který výstup.

Signál

Pouze u těch nejjednodušších obvodů lze připojit vstupy a výstupy komponenty přímo na vstupy a výstupy obvodu. Šlo to například u naší neúplné sčítačky. Tam jsou dvě hradla, XOR a AND, a u obou jsou vstupy připojené k vstupům sčítačky, výstupy k výstupům. Co ale v situaci, kdy chceme připojit (například) vstup jednoho hradla na výstup druhého? Narazíme na to, že pro podobné propojení „nemáme jméno do port mapy“. VHDL proto zavádí koncept signálu. Signál je „interní vodič“ – můžete si ho představit jako fyzický vodič, kterým jsou propojeny jednotlivé komponenty v obvodu. Signál má stejné typy jako vstup a výstup z entity, jen nemá určený směr (protože nevede mimo obvod). Dalo by se také říct, že i vstupy a výstupy v entitě jsou specifické signály, které mají určený směr. Můžeme tak hovořit o „vstupně – výstupních signálech“ (až doteď jsem se tomuto označení bránil, právě proto, aby se nepletly „vstupní signály“ se „signálem“). Signály rovněž umožňují zavést zpětnou vazbu, totiž data z „výstupu“ přivést opět na vstup nějaké komponenty. (Tady ale upozorňuju na jednu záludnost, ke které se vrátím, a ta zní latch…) Ve VHDL totiž nelze v port map namapovat „výstup z entity“ na „vstup do vnitřní komponenty“. Dosti teorie. Pojďme k praxi. Popišme si „plnou sčítačku“. Plná sčítačka se od naší neúplné liší tím, že pracuje i se vstupním přenosem (Cin). Má tedy tři vstupy (A, B, Cin) a dva výstupy (Q, Cout). Můžeme si zase udělat pravdivostní tabulku a poskládat si sčítačku z hradel, nebo můžeme zvolit ten přístup, kdy pomocí naší neúplné sčítačky sečteme A a B, a k takto vzniklému mezivýsledku přičteme vstupní přenos. Výsledný přenos je dán přenosem z jednoho nebo druhého sčítání (pokud se vyskytne, bude i na výstupu).

A B Cin Q Cout
0 0 0 0 0
0 1 0 1 0
1 0 0 1 0
1 1 0 0 1
0 0 1 1 0
0 1 1 0 1
1 0 1 0 1
1 1 1 1 1

fulladder Ze schématu je patrné, jak jsou na sebe bloky napojené. Máme pět vstupně – výstupních signálů (Cin, A, B, Q, Cout). Uvnitř jsou tři další spoje: mezi výsledkem první sčítačky a vstupem B druhé (Subtotal), a pak mezi jednotlivými přenosy a hradlem OR (C1 a C2). Pojďme si tedy napsat plnou sčítačku. Začneme opět deklarací:

Není tam nic, co by nám bylo neznámé. Entita se jmenuje fulladder a její porty jsou velmi podobné naší sčítačce, jen je přidaný port Cin. Druhá část bude architektura. Začneme hlavičkou:

Po hlavičce musí přijít popis použité komponenty. Na rovinu přiznávám, že to je kopie deklarace z entity, přejmenovaná na component.

Nyní je na místě nadeklarovat si signály. Syntax je:

Tedy takto:

Nic víc nepotřebujeme, pojďme popsat chování. Tu část uvozuje, jak už víme, toto:

Nyní si vytvoříme dvě instance sčítačky (komponenta adder) a nastavíme propojení:

Drobná pauza… Všimněte si zápisu. Je to jméno instance, dvojtečka, jméno komponenty, klíčová slova port map a v závorce seznam signálů. Ten seznam má tolik položek, kolik položek má port v komponentě adder (tedy čtyři) a ve stejném pořadí, v jakém jsou v komponentě, jim přiřazuju nějaké signály uvnitř nového obvodu. Tedy: ADDER1 je první (levá) sčítačka. Vstup A je zapojen na vstupní signál A, totéž se vstupem B, výstup Q je připojen na signál („vodič“) Subtotal, tedy mezisoučet, a výstup Cout je připojen na signál C1. Pro druhou instanci sčítačky platí, že vstup A bere signál Cin, vstup B bere to, co je na signálu Subtotal, výstup Q je připojen na stejnojmenný výstup celého obvodu, no a výstup Cout tvoří druhý signál přenosu C2. Ukážeme si ještě alternativní zápis, kde nezáleží na pořadí. V něm v port map použijeme tvar „port komponenty => místní signál“

Takto tedy vypadá strukturální popis architektury. Zbývá už jen logický součet (OR), který vezme C1 a C2 a výsledek pošle na výstup Cout. Můžeme si vytvořit komponentu s funkcí OR, udělat její instanci a pomocí port map určit, jak bude připojena. Ale mnohem snazší je přimíchat trochu data flow:

A to stačí. Máme popsané vše, co je v obvodu použité, takže nezbývá než udělat pápá:

Za domácí úkol si napište testbench pro tuto sčítačku a pomocí Multisimu si otestujte průběhy signálů.

Přiřazení signálů

Zatím jsme si ukázali to nejjednodušší přiřazování, kdy nějakému signálu je přiřazena hodnota operátorem <=. Na pravé straně může být výraz, a syntetizér se ho pokusí převést do ekvivalentní realizace v obvodové podobě. Výraz může obsahovat základní matematické a logické operátory (+, -, AND, OR, NOT…), závorky a další věci, na které jsme zvyklí z programovacích jazyků. Druhá forma je podmíněné přiřazení. Jeho forma je takováto:

Podmínka není nic jiného než výraz, který je vyhodnocen na log. 1, nebo log. 0. Tedy například:

Čteme jako: Q bude 0, pokud A=B, jinak 1. V podstatě je to část naší neúplné sčítačky. Všimněte si, že se porovnávání zapisuje jednoduchým rovnítkem, nikoli zdvojeným. Další zdroj častých chyb u programátorů, co přecházejí z „C-like“ světa. Složitější příklad:

Opět slovy: Q je nula, pokud A=B a Cin je nulové. Pokud není, tak Q je nula, pokud A i B jsou 0 a Cin je 1. Jinak je Q = 1. U jednobitového výrazu to tak nevynikne, ale u vícebitových, ke kterým se dostaneme příště, bude ta výhoda patrná. Třetí možná forma je syntaktický cukr pro druhou v případě, že se rozhodujeme podle jednoho signálu.

Trošku to připomíná známou konstrukci switch-case z programovacích jazyků. Poslední řádek definuje, co se stane, pokud bude výsledek rozhodovacího výrazu jiný než některá z možností (obdoba „default“). U naší plné sčítačky se například mohu rozhodnout podle signálu Cin, a pokud bude 0, tak výsledek nastavím podle výrazu A=B, pokud je Cin 1, výsledek bude A/=B. (Bod má ten, kdo si tipnul, že /= znamená „nerovná se“.) Tedy takto:

Nemůžu zapsat (A = B), protože výsledek porovnání není logická hodnota a nelze jej syntakticky jednoduše na logickou hodnotu převést, proto zapisuju pomocí XOR a NOT XOR.

Help I Accidentally Build A Latch

Jestli vám až do této chvíle připadal latch jako úplně normální součástka, jakých jsou plné katalogy (SN7475 například), tak po téhle podkapitole se váš pohled na něj změní. Latch je součástka, která má dva vstupy, datový a řídicí. Když je na řídicím log. 1, je obvod průchozí a „co na vstupu, to na výstupu“. Jakmile se změní řídicí vstup na 0, tak obvod drží na výstupu poslední hodnotu před touto změnou. Pamatuje si. Což je docela užitečná funkce, pokud ji potřebujeme použít. V takovém případě o ní víme, VHDL syntetizér správně takovou funkci převede do obvodové podoby, u FPGA od Altery to zabere jeden logický element, a je to v pořádku. Problém je, když latch vznikne takříkajíc „bez našeho přičinění“. Jak? No, stačí drobnost: Zapomeneme ošetřit všechny možné kombinace na vstupech! Představme si, pro tu úplnou jednoduchost nejjednodušší, že zapisuju neúplnou sčítačku a zvolím k tomu podmíněné přiřazení „when“. Správný zápis bude:

Jenže co když zapomenu na tu „default“ část?

Tady to je málo pravděpodobné, ale u složitějších obvodů se to může stát. Myslíte si, že jste ošetřili všechno, nestalo se tak. Nebo u behaviorálního popisu zapomenete v nějaké větvi nastavit nějaký výstup. Co se stane? VHDL si s tím poradí jednoduchou úvahou: Pokud neřeknete, jakou má mít výstup hodnotu, necháme tam takovou, jaká byla! To je klasický přístup z programovacích jazyků: Když neřeknu, že se to má změnit, tak se to nemění. Jenže elektronický obvod nemá nic jako „neměň výstup“, a pokud chci, aby se výstup nezměnil, tak si musím jeho hodnotu někde zapamatovat. Kde? No, zkuste hádat! A aniž byste to chtěli, tak vznikne latch, většinou zcela nadbytečný, a zabírá místo v návrhu. „Omylem vytvořený latch“ je něco jako memory leak, neuvolněný zdroj nebo omylem postavená police. help-i-accidentally-build-a-shelf Zkrátka chyba v návrhu, kterou syntetizér sice nějak vyřeší, ale za cenu zbytečného mrhání drahocennými logickými elementy. Většinou to znamená, že jste zapomněli na nějaké přiřazení výstupu za nějakých podmínek. U prostého přiřazení se to nestává, ale jakmile použijete podmíněné přiřazení nebo podmínky jako takové, může se to stát. Nejen že to vygeneruje latch navíc, ale taky to často znamená, že výsledné zapojení nebude fungovat tak, jak má. Proto pozor na takovéhle situace a vždy výstupům přiřaďte nějakou hodnotu!