Fakulta elektrotechniky a informatiky Univerzita Pardubice
Souhrn Apendixu A – doporučení VHDL Práce ke zkoušce z předmětu Programovatelné logické obvody
Jméno: Jiří Paar Datum: 17. 2. 2010
Poznámka k jazyku VHDL Jazyk VHDL byl původně vytvořen pro dokumentování a simulaci obvodů, spíš než pro popis syntézy obvodů. Proto je jeho syntaxe v některých ohledech trochu neobvyklá. Následující kapitoly obsahují pouze základní popis jazyka VHDL. Jak „nepsat“ kód v jazyce VHDL Začínající uživatelé jazyka VHDL mají tendenci psát kód stejným způsobem jako pro program pro počítač, který obsahuje mnoho proměnných a cyklů. Následující kapitoly budou zaměřeny na základní popis syntaxe jazyka VHDL, kde budou na první pohled vidět rozdíly mezi jazykem VHDL a programem pro počítač. Jelikož je jazyk VHDL velmi složitý, jsou chyby v syntaxi, i pro zkušeného uživatele, celkem běžné. Je užitečné zkoumat již hotové obvody ve snaze jim porozumět a tím více pochopit samotný jazyk VHDL.
Tvorba dokumentace v jazyce VHDL Jazyk VHDL může obsahovat dokumentaci prostřednictvím komentářů. Každý komentář začíná dvěma znaky '-'. Překladač jazyka VHDL následně všechen text, který začíná znaky '--' , ignoruje. Příklad: -- ukázka komentáře v jazyce VHDL
Datové objekty V jazyce VHDL jsou všechny informace reprezentovány jako datové objekty. Datové objekty se dělí na signály (signal), konstanty (constant) a proměnné (variable). Pro popis logických obvodů se používají datové objekty v podobě signálů, které představují logické signály v obvodu. Konstanty a proměnné jsou také někdy používány pro popis logických obvodů, ale pouze zřídka.
Názvy datových objektů Pravidla pro specifikování názvů datových objektů jsou jednoduchá, lze použít všechny alfanumerické znaky. Existují také některé výjimky. Jako jméno nemůže být použito některé z klíčových slov, musí začínat písmenem, nemůže končit podtržítkem a nemůže mít v názvu dvě po sobě jdoucí podtržítka. Jako příklad správného názvu lze uvést příklad: x, x1, x_y a Byte. Ukázka nesprávného pojmenování datového objektu: 1x, _y, x__y a entity. Poslední název nelze použít z toho důvodu, že se jedná o klíčové slovo jazyka VHDL. Je nutné poznamenat, že jazyk VHDL není „case sensitive“, tedy název x je totožný s názvem X a ENTITY je shodné s entity.
Hodnoty a čísla datových objektů Jako datový objekt použijeme signál (pro reprezentaci jednoho logického signálu v obvodu), mnohonásobné signály a binární čísla (celá čísla). Hodnoty jednoho signálu se zapisují mezi dvojici apostrofů (př. '0' nebo '1'). Hodnoty vícenásobných signálů se zapisují mezi dvojici uvozovek "". Jako příklad lze uvést čtyř-bitový signál "1001" nebo osmi-bitový signál "10011000". Uvozovkami je tedy vyjádřeno binární číslo. Takové číslo je možné vyjádřit i
pomocí čtyřech signálů s hodnotami '1', '0', '0', '1', nebo také celím číslem (1001)2 = (9)10. Celé číslo lze zapsat také přímo bez použití uvozovek, např. 9 nebo 152. Stejnými způsoby se zapisují hodnoty pro konstanty i pro proměnné.
Datový objekt SIGNAL Jak již bylo zmíněno, tento datový objekt představuje logické signály, nebo také vodiče (dráty), v obvodu. Existují tři možné umístění deklarace signálu, v deklaraci entity, v deklaraci architektury a v deklaraci balíčku. Deklarace signálu vypadá následovně: SIGNAL nazev_signalu : typ_signalu; typ_signalu představuje správnou hodnotu, kterou může signál v rámci VHDL kódu nabývat.
V následujících podkapitolách bude popsáno 9 typů signálů: BIT, BIT_VECTOR, STD_LOGIC, STD_LOGIC_VECTOR, SIGNED, UNSIGNED, INTEGER, ENUMERATION a BOOLEAN.
Typy BIT a BIT_VECTOR Tyto typy jsou předdefinovány ve standardech VHDL IEEE 1076 a IEEE 1164, proto není potřeba žádná knihovna pro použití těchto typů. Objekty typu BIT mohou nabývat hodnot '0' nebo '1' a objekt typu BIT_VECTOR je lineární pole objektů typu BIT. Příklad deklarace: SIGNAL x1 : BIT; SIGNAL C : BIT_VECTOR (1 TO 4); SIGNAL Byte : BIT_VECTOR (7 DOWNTO 0);
Signály C a Byte představují dva způsoby definice mnohobitového datového objektu. Syntaxe „nejnižší-index TO nejvyšší-index“ lze použít pro mnohabitové signály jako pole bitů, nejvýše významný bit je totožný s bitem „nejvyšší-index“ a nejméně významný bit je totožný s bitem „nejnižší-index“. Syntaxe „nejvyšší-index DOWNTO nejnižší-index“ se používá při deklaraci binárních čísel. V tomto případě je nejvíce významný bit totožný s bitem „nejvyšší-index“ a nejméně významný bit s bitem „nejnižší-index“. Přiřadit hodnotu vícebitovému objektu lze dvěma způsoby. Přímé přeřazení C <= "1010" nebo přiřazení po jednotlivých bitech (signálech) C(1) = '1', C(2) = '0', C(3) = '1' a C(4) = '0'. Pro datový objekt Byte je přiřazení např. následující Byte <= "10011000", tedy jednotlivé signály mají tyto hodnoty: Byte(7) = '1', Byte(6) = '0', ..., Byte(0) = '0'.
Typy STD_LOGIC a STD_LOGIC_VECTOR Typ STD_LOGIC byl přidán do standardu VHDL IEEE 1164. Tento typ poskytuje větší flexibilitu než typ BIT. Pro použití tohoto typu je potřeba vložit knihovnu std_logic_1164 následujícím způsobem: LIBRARY ieee; USE ieee.std_logic_1164.all;
Tento postup zajistí přístup do balíčku std_logic_1164, který definuje typ STD_LOGIC. Tento typ může nabývat pouze těchto hodnot: 0, 1, Z, –, L, H, U, X a W. Pouze první čtyři hodnoty lze použít v logických obvodech. Hodnota Z znamená vysokou impedanci, hodnota – znamená „nepoužito“. Hodnota L je stejné vyjádření pro 0 a hodnota H pro 1, hodnota U znamená, že není inicializována, hodnota X je pro neznámou hodnotu a hodnota W je pro tzv. „měkkou neznámou hodnotu“. Typ STD_LOGIC_VECTOR představuje pole typu STD_LOGIC. Příklad deklarace: SIGNAL x1, x2, Cin, Cout, Sel SIGNAL C SIGNAL X, Y, S
: STD_LOGIC; : STD_LOGIC_VECTOR(1 TO 4); : STD_LOGIC_VECTOR(3 DOWNTO 0);
Typ STD_LOGIC je často používán pro logické výrazy. Typ STD_LOGIC_VECTOR aby mohl být použit jako binární číslo v aritmetických obvodech, je potřeba vložit další knihovnu: USE ieee.std_logic_signed.all;
V tomto balíčku je specifikováno správné použití typu STD_LOGIC_VECTOR s aritmetickými operátory, jako je např. +. Takto překladač vygeneruje obvod pracující s čísly se znaménkem. Při použití balíčku std_logic_unsigned vygeneruje překladač obvod, který bude pracovat s čísly bez znaménka.
Typy SIGNED a UNSIGNED Knihovny std_logic_signed a std_logic_unsigned používají další knihovnu std_logic_aritm, která definuje typy pro obvody využívající aritmetické operace, např. sčítání. Navíc definuje dva typy signálů, SIGNED a UNSIGNED. Tyto typy jsou identické s typem STD_LOGIC_VECTOR, protože představují pole typu STD_LOGIC. Účelem těchto typů je pouze zpřehlednění kódu, protože se jimi dává najevo, že se bude pracovat s čísly. Typ SIGNED se použije v případě obvodů, kde se pracuje s čísly se znaménkem (dvojkový doplněk) a typ UNSIGNED naopak v obvodech, kde se pracuje s čísly bez znaménka.
Typ INTEGER Standard VHDL definuje typ INTEGER pro práci s aritmetickými operátory. Typ INTEGER reprezentuje binární číslo, při deklaraci lze určit kolik bitů bude ve výsledku signál mít, stejně jako pro typ STD_LOGIC_VECTOR. Pro určení velikosti typu INTEGER se používá klíčové slovo RANGE. Výchozí délka typu INTEGER je 32bitů. Standard VHDL také umožňuje určit velikost záporného a kladného čísla. Příklad deklarace: SIGNAL X : INTEGER RANGE –127 TO 127;
Typ BOOLEAN Objekt typu BOOLEAN může nabývat pouze hodnoty TRUE nebo FALSE. Hodnota TRUE je stejná s 1 a FALSE s 0.
Typ ENUMERATION Tento typ může nabývat pouze určitých kladných čísel. Základní tvar typu ENUMERATION: TYPE nazev_typu_enumeration IS (nazev1 [, nazev2]);
Typ ENUMERATION se nejvíce využívá při tvorbě stavových automatů, kde obsahuje aktuální nebo následující stav. Příklad deklarace: TYPE typ_stavu IS (stavA, stavB, stavC); SIGNAL y : typ_stavu;
Signál y může nyní nabývat pouze hodnot stavA, stavB nebo stavC.
Datový objekt CONSTANT Konstantní datový objekt CONSTANT představuje klasickou konstantu, tedy signál, který nemůže měnit svoji hodnotu. Základní tvar typu CONSTANT: CONSTANT nazev : typ := hodnota;
Příklad použití: CONSTANT Zero : STD_LOGIC_VECTOR(3 DOWNTO 0) := "0000"
Takto lze použít slovo Zero v kódu všude tam, kde bude potřeba konstanta "0000".
Datový objekt VARIABLE Proměnná, neboli datový objekt VARIABLE, na rozdíl od signálu nutně nepředstavuje „drát“ v obvodu. Datový objekt VARIABLE se používá např. pro uchování hodnoty ve výpočtech nebo jako „indexová“ proměnná v cyklech.
Typové konverze Jazyk VHDL je velice typově bezpečný, nelze tedy signál jednoho typu přiřadit signálu jiného typu, i když se jedná i podobné typy, např. typ BIT a STD_LOGIC. V případech kdy je potřeba přetypovat jeden typ na druhý, musejí se použít k tomu určené funkce. Např. signál X má osm bitů typu STD_LOGIC a Y je typu INTEGER definovaný v rozsahu 0 až 255 (oba mají osm bitů), pro tento případ lze použít tuto funkci: X <= CONV_STD_LOCIG_VECTOR(Y,8);
Tato funkce má dva parametry, první je signál, který chceme převést, a druhý parametr je do kolika bitů signálu X chceme uložit výsledek. Pozn. tato funkce je deklarována v knihovně std_logic_aritm.
Pole Jak bylo již řečeno typy BIT_VECTOR a STD_LOGIC_VECTOR jsou ve skutečnosti pole typu BIT nebo STD_LOGIC. Ve standardech VHDL jsou tyto pole definovány následovně: TYPE BIT_VECTOR IS ARRAY (NATURAL RANGE <>) OF BIT; TYPE STD_LOGIC_VECTOR IS ARRAY (NATURAL RANGE <>) OF STD_LOGIC;
Velikost těchto typů není definována, to zaručuje syntaxe (NATURAL RANGE <>), která má vliv na to, že si výslednou velikost bude moci vybrat sám uživatel. Pole jakéhokoli typu si může uživatel vytvořit sám např. takto:
TYPE Byte IS ARRAY (7 DOWNTO 0) OF STD_LOGIC; SIGNAL y : Byte;
Signál y bude mít osm bitů typu STD_LOGIC. Lze také deklarovat dvou rozměrné pole: TYPE RegArray IS ARRAY (3 DOWNTO 0) OF STD_LOGIC_VECTOR(7 DOWNTO 0); SIGNAL R : RegArray;
Tento kód definuje R jako pole čtyř elementů, každý element je osmi bitový typ STD_LOGIC_VECTOR. Syntaxe R(i) (3 ≥ 𝑖𝑖 ≥ 0) získá jeden ze čtyř elementů a R(i)(j) (3 ≥ 𝑖𝑖 ≥ 0, 7 ≥ 𝑗𝑗 ≥ 0) získá přístup k j-tému bitu i-tého elementu, tento bit je typu STD_LOGIC.
Operátory Jazyk VHDL umožňuje použít řadu operátorů: Priorita Třída operátorů Vysoká Různé Násobící Znaménkové Sčítací Relační Nízká Logické
Operátory **, ABS, NOT *, /, MOD, REM +, – +, –, & =, /=, <, <=, >, >= AND, OR, NAND, NOR, XOR, XNOR
Operátory se rozlišují podle třídy a každá třída má svoji prioritu, např. operátor NOT a AND mají různou prioritu, operátor NOT má vyšší prioritu. Je potřeba si dávat pozor na logické výpočty s operátory ze stejné třídy, tedy se stejnou prioritou. Logické operátory jsou vyhodnocovány zleva doprava. Např.: x1 AND x2 OR x3 AND x4 není stejný s aritmetickým zápisem x1.x2 + x3.x4 naopak je roven výrazu (x1.x2+x3).x4,
správný výraz je potřeba zapsat následovně: (x1 AND x2) OR (x3 AND x4)
Návrh entity ve VHDL Obvod nebo podobvod popsaný ve VHDL je nazýván entitou. Každá entita má dvě části, první část je deklarace entity (entity), která určuje vstupní a výstupní signály, a druhou částí je architektura (architecture), která určuje chování entity. Entita Deklarace entity Architektura
Deklarace entity V deklaraci entity se určují vstupní a výstupní signály. Název entity může být pouze vhodný název v rámci jazyka VHDL. Vstupní a výstupní signály mohou být specifikovány pouze v rámci klíčového slova PORT, jak ukazuje následující ukázka: ENTITY nazev_entity IS PORT ([SIGNAL] signal_name {, signal_name} : [mode] typ_signalu{; [SIGNAL] signal_name {, signal_name} : [mode] typ_signalu}); END nazev_entity;
Port může být vstupní (IN), výstupní (OUT) nebo obousměrný (INOUT) (v ukázce definováno na místě mode), pokud není směr portu definován, je považován za vstupní.
Architektura Tato část entity popisuje výsledné chování entity. Základní tvar architektury vypadá takto: ARCHITECTURE nazev_architektury OF nazev_entity IS [deklarace signálů] [deklarace konstant] [deklarace uživatelských typů] [deklarace komponent] [deklarace atributů] BEGIN {instance komponent; } {paralelní příkazy; } {proces; } END [nazev_architektury];
Každá architektura má dvě hlavní části, část pro deklarace (signálů, konstant, apod.) a tělo architektury, kde je umístěna hlavní funkčnost entity.
Paralelní příkazy Paralelní příkazy přiřazují hodnoty signálům v těle architektury. Jazyk VHDL rozlišuje celkem čtyři typy paralelních příkazů.
Jednoduché přiřazení Jednoduché přiřazení se používá pro logické nebo aritmetické výpočty. Základní tvar jednoduchého přiřazení: nazev_signalu <= vyraz;
Symbol <= je přiřazovací operátor, který lze použít buď na jednotlivé signály: SIGNAL x1, x2, x3, f : STD_LOGIC; f <= (x1 AND x2) OR x3;
Nebo také pro vícebitové signály: SIGNAL A, B, C : STD_LOGIC_VECTOR(1 TO 3); C <= A AND B;
Přiřazení hodnoty prostřednictví příkazu OTHERS Pokud je potřeba nějaký více bitový signál např. vynulovat, můžeme to provést pomocí příkazu S <= "0000". Pokud má, ale signál mnoho bitů je možné použít příkaz OTHERS →
S <= (OTHERS => '0');. Tento příkaz vynuluje všechny bity signálu S, ať je jeho velikost
jakákoli.
Výběrové přiřazení Výběrové přiřazení se používá pro přiřazení hodnoty signálu podle hodnoty jiného signálu: WITH hodnota_signalu SELECT nazev_signalu <= signal1 WHEN konstanta; signal2 WHEN OTHERS;
Jako ukázkový příklad se hodí např. dvou vstupý multiplexer: SIGNAL x1, x2, Sel, f : STD_LOGIC; WITH Sel SELECT f <= x1 WHEN '0', x2 WHEN OTHERS;
Tento kód funguje tak, že podle hodnoty signálu Sel se přiřazují signálu f hodnoty signálů x1 nebo x2. Je-li Sel = '0' → f = x1 a pro jiné hodnoty Sel → f = x2.
Podmíněné přiřazení Této způsob přiřazení je podobný výběrovému. Podmíněné přiřazení přiřazuje signálu jednu z možných hodnot, nikoli hodnoty jiných signálů. Základní tvar: nazev_signalu <= vyraz WHEN logicky_vyraz ELSE {vyraz WHEN logicky_vyraz ELSE} vyraz;
Příklad použití: SIGNAL x1, x2, x3 : STD_LOGIC; SIGNAL f : STD_LOGIC_VECTOR (1 DOWNTO 0); f <= '00' WHEN x1 = '1' ELSE '01' WHEN x2 = '1' ELSE '10' WHEN x3 = '1' ELSE '11';
Tento kód nastavuje hodnotu signálu f podle hodnot jiných signálů. Bude-li signál x1 = '1' bude se f = '00', apod. V případě, že hodnoty signálů x1 a x2 budou '1' bude se signál f = '00', protože hodnota signálu x1 má vyšší prioritu.
Generující příkaz Existují dvě varianty generujícího příkazu (GENERATE), FOR GENERATE a IF GENERATE. Příkaz IF GENERATE se používá jen velmi zřídka, ale příkaz FOR GENERATE se používá často. Ukázky použití: FOR index_citace IN rozsah GENERATE vyraz; {vyraz;} END GENERATE; IF vyraz GENERATE vyraz; {vyraz;} END GENERATE;
Sekvenční příkazy Sekvenční příkazy jsou dalším typem příkazů, stejně jako paralelní příkazy, které se umisťují do těla architektury. Paralelní příkazy sice umožňují tvorbu většiny logických obvodů, nicméně jazyk VHDL poskytuje také sekvenční příkazy, které lze použít při návrhu kombinačních i sekvenčních logických obvodů. Existují tři příkazy (IF, CASE, LOOP) pro použití se sekvenčními příkazy.
Příkaz PROCESS Jedná se o příkaz, který odděluje paralelní příkazy od sekvenčních příkazů. Pochopitelně je umístěn také v těle architektury a zahrnuje další příkazy, jako jsou příkazy IF, CASE a LOOP, které mohou být umístěny pouze v rámci příkazu PROCESS. Základní tvar příkazu PROCESS: PROCESS [(nazev_signalu{, nazev_signalu})] [deklarace proměnných] BEGIN [příkaz WAIT] [jednoduché přiřazení signálů] [přiřazení proměnných] [příkaz IF] [příkaz CASE] [příkaz LOOP] END PROCESS;
Struktura příkazu PROCESS je podobná se strukturou příkazu ARCHITECTURE. Jedině v rámci příkazu PROCESS mohou být deklarovány proměnné (VARIABLE) a jedině v rámci procesu mohou být i použity, při nutnosti použití proměnné mimo proces je nutné hodnotu proměnné přiřadit nějakému signálu. Za příkazem PROCESS se může vyskytnout tzv. citlivostní seznam. Změna některého ze signálů uvedených v tomto seznamu vyvolá spuštění procesu. Není-li citlivostní seznam uveden je nutné v rámci procesu použít příkaz WAIT. Příkazy IF, CASE nebo LOOP umožňují vytvoření popisu kombinačního nebo sekvenčního obvodu. Při tvorbě kombinačních obvodů musí být v citlivostním seznamu uvedeny všechny vstupní signály. Příklad ukázky kombinačního obvodu: PROCESS (Sel, x1, x2) BEGIN IF (Sel = '0') THEN f <= x1; ELSE f <= x2; END IF; END PROCESS;
Toto je ukázka vytvoření dvou vstupého multiplexeru.
Příkaz IF Tento příkaz je velice podobný příkazu if z jiných programovacích jazyků. Základní tvar: IF logicky_vyraz THEN prikaz; {prikaz;} ELSIF logicky_vyraz THEN prikaz; {prikaz;} ELSE prikaz; {prikaz;} END IF;
Při splnění jedné z podmínek se provedou dané příkazy, pokud není splněna žádná podmínka, budou provedeny příkazy za příkazem ELSE. Příkazy ELSIF ani ELSE nemusejí být použity.
Příkaz CASE Základní tvar příkazu CASE: CASE nazev_signalu IS WHEN konstanta => prikaz; {prikaz;} WHEN konstanta => prikaz; {prikaz;} WHEN OTHERS => prikaz; {prikaz;} END CASE;
Tento příkaz v závislost na hodnotě signálu nazev_signalu vykonává jednotlivé příkazy. Příkaz CASE musí zahrnovat všechny možné hodnoty signálu, proto je vhodné použít příkaz OTHERS. Příklad použití: CASE Sel IS WHEN '0' => f <= x1; WHEN OTHERS => f <= x2; END CASE;
Takto navržený obvod je totožný s obvodem, který byl uveden v příkladu ukázky kombinačního obvodu při použití příkazu PROCESS.
Příkaz LOOP Jazyk VHDL umožňuje dva typy příkazů pro tvorbu cyklů (LOOP), příkazy FOR-LOOP a WHILELOOP. Tyto příkazy jsou použity pro opakování jednoho nebo více sekvenčních příkazů. Základní tvary příkazů: FOR nazev_promenne IN rozsah LOOP prikaz; {prikaz;} END LOOP; WHILE logicky_vyraz LOOP prikaz; {prikaz;} END LOOP;
Příkaz FOR-LOOP se používá pro případy, kdy je znám počet opakování. Zato příkaz WHILELOOP se používá v případech, kdy není přesně znám počet opakování.