Obsah 0. Úvod ..................................................................................................................................................... 4 1. VHDL stylem "Dataflow" .................................................................................................................... 5 1-1 Majorita ze tří — úvod do VHDL a prostředí Quartus II ..........................................................5 1-1.a) Schéma obvodu v symbolickém editoru ...........................................................................5 1-1.b) Vytvoření VHDL kódu .....................................................................................................5 1-1.c) Jak VHDL program použijeme v grafickém editoru schémat? ........................................6 1-1.d) Vysvětlení VHDL majority: knihovny na řádku 2 až 3....................................................7 1-1.e) Vysvětlení VHDL majority: Blok entita — řádky 5 až 12 ...............................................8 1-1.f) Vysvětlení VHDL majority: Definice port — řádky 6 až 9 ..............................................8 1-1.g) Vysvětlení VHDL majority: Architektura — řádky 12 až 15 ........................................10 1-1.h) Vysvětlení VHDL majority: Příkazový blok— řádka 14 ...............................................11 1-2 Rozšířená majorita ze tří — VHDL signály ...........................................................................11 1-2.a) Postup rozšíření VHDL majority o další výstup .............................................................11 1-2.b) O definici "signal" na řádce 13 .......................................................................................13 1-2.c) Více o <= "concurrent assignment" ................................................................................14 1-2.d) Otestování upravené majority.........................................................................................14 1-2.e) Co když potřebujeme nerozšířenou verzi majority? .......................................................15 1-3 Dekodér pro ukazatel — příkazy with…select jazyka VHDL ................................................16 1-3.a) Příkaz with…select .........................................................................................................16 1-3.b) Datový typ std_logic versus std_logic_vector ................................................................17 1-3.c) Tvorba vlastních typů a atributy VHDL .........................................................................18 1-3.d) Dekodér pro lineární ukazatel s výstupy na jednotlivé vodiče .......................................19 1-4 Prioritní dekodér — VHDL příkaz when…else .....................................................................20 1-4.a) Operátory porovnání a logické operátory ve VHDL ......................................................21 1-4.b) Pohled do nitra překladače přes RTL Viewer ................................................................22 1-5 Tříbitový prioritní inhibitor — cyklus for-generate ................................................................23 1-5.a) Jak vložit do výjimky do VHDL — příkazy generic a assert ........................................25 1-5.b) Co programu ještě chybí? ...............................................................................................26 2. VHDL stylem "Structural" ................................................................................................................ 27 2-1 Použití prioritního inhibitoru ve VHDL .................................................................................27 2-1.a) Sekce port map a generic map .........................................................................................28 2-1.c) Sekce map a schéma ........................................................................................................29 2-1.c) Mapování vstupů a výstupů pro desku DE2 ....................................................................30 2-2 Rozšíření prioritního inhibitoru o enable ...............................................................................30 2-2.a) Inicializace proměnných vektorů asociací others=> .......................................................31 2-3 Vytvoření vlastní knihovny ("package") .................................................................................32 2-3.a) Dekodér pro lineární ukazatel s výstupy na jednotlivé bity pomocí knihovny ..............33 2-4 Zapojení prioritního dekodéru a lineárním ukazatelem ..........................................................33 Závěr....................................................................................................................................................... 35 Příloha A: Testbench pro ModelSim ..................................................................................................... 36 A-1 Vytvoření programu typu Testbench ......................................................................................36 A-2 Spuštění Testbench v ModelSim Altera..................................................................................39
2
Seznam programů Program 1 - Majorita ze tří ...................................................................................................................6 Program 2 - Pořadí vstupů a výstupů v entitě a ve schematické značce ............................................10 Program 3 - Majorita s pomocným výstupem jednoho signálu .........................................................12 Program 4 - Dekodér pro lineární ukazatel ........................................................................................16 Program 5 - Dekodér pro ukazatel s oddělenými výstupy .................................................................19 Program 6 - Prioritní dekodér.............................................................................................................20 Program 7 - Zbytečné podmínky v příkazu when...else .....................................................................21 Program 8 - Program pro demonstraci práce s proměnnými datového typu Boolean .......................21 Program 9 - Minimalizovaná kaskáda multiplexorů ..........................................................................22 Program 10 - Java metoda pro vyzkoušení prioritního inhibitoru .....................................................23 Program 11 - Prioritní inhibitor pro 18 vstupů a výstupů ..................................................................24 Program 12 - Prioritní inhibitor s generic ..........................................................................................25 Program 13 - Prioritní inhibitor ve strukturálním popisu ...................................................................27 Program 14 - Nedoporučené použití neúplných definic v bloku "component"! ................................28 Program 15 - Automaticky generovaný VHDL kód ..........................................................................29 Program 16 - Prioritní inhibitor s enable vstupem .............................................................................30 Program 17 - Demonstrace použití others pro inicializaci .................................................................31 Program 18 - Knihovna kódů vytvořených v úvodu do VHDL .........................................................32 Program 19 - Dekodér pro lineární ukazatel s výstupy na jednotlivé bity pomocí knihovny ............33 Program 20 - Prioritní dekodér s výstupem na lineární ukazatel .......................................................34 Program 21 - VHDL testbech pro dekoder dekodér s výstupem na lineární ukazatel .......................37 Seznam obrázků Obrázek 1 - Majorita ze tří ..................................................................................................................5 Obrázek 2 - Realizace módů v obvodu ................................................................................................9 Obrázek 3 - Schéma odpovídající přiřazení y <= a AND b OR a AND c OR b AND c; .........11 Obrázek 4 - Upravená majorita ..........................................................................................................13 Obrázek 5 - Důsledek chybného dvojího přiřazení hodnot do signálu ytmp .....................................13 Obrázek 6 - Aktualizace změněného symbolu ...................................................................................14 Obrázek 7 - Výsledné schéma v test.bdf ............................................................................................15 Obrázek 8 - Rozšířená majorita s nezapojeným výstupem y1 ...........................................................15 Obrázek 9 - Operace s std_logic_vector ............................................................................................17 Obrázek 10 - Dekodér pro lineární ukazatel s bitovými I/O ..............................................................19 Obrázek 11 - Prioritní dekodér z multiplexorů ..................................................................................20 Obrázek 12 - Vyvolání RTL Viewer z top-level entity......................................................................22 Obrázek 14 - Ukázka použití prioritního inhibitoru ve schématu ......................................................25 Obrázek 13 - Příklad použití prioritního inhibitoru ...........................................................................25 Obrázek 15 - Příkaz "Create HDL Design File" ................................................................................29 Obrázek 16 - Test inhibitoru 2 vložený do schématu.........................................................................30 Obrázek 17 - Prioritní dekodér s lineárním ukazatelem .....................................................................33 Obrázek 18 - Breakpoint při simulaci ................................................................................................42
3
0. Úvod Proč zrovna VHDL? VHDL patří mezi HDL programovací jazyky ("Hardware Description Languages") určené pro návrhy obvodů. V USA se převážně používá HDL jazyk Verilog, se syntaxí podobnou jazyku C, zatímco v Evropě se více programuje ve VHDL ("Very-high-speed integrated circuits Hardware Description Language"), vzdáleně se podobajícímu PASCALu. (Přesněji — VHDL si vypůjčilo mnoho své syntaxe z jazyka ADA, u nás méně známého, který se mírně podobá PASCALu.) Když jsme se v roce 2009 rozhodovali, zda studenty učit VHDL či Verilog, mnozí profesionální návrháři, kteří léta používají oba jazyky, se klonili k VHDL. Jde o jazyk sice upovídaný a typově ještě striktnější než PASCAL, avšak návrhy v něm mají lepší šanci fungovat. Jazyk Verilog má ve své podobnosti s jazykem C jen zdánlivou výhodu; postupy osvojené z C programů se nehodí pro obvody. Zde si nutno poznamenat, že HDL jazyky mají trojí použití: 1/ pro specifikaci obvodů, 2/ pro jejich simulaci a 3/ pro jejich fyzickou syntézu. Kvůli tomu obsahují široké spektrum programových konstrukcí, ale pro syntézu obvodů se hodí jen některé z nich. Účel učebního textu Následující text vznikl na základě dlouholetých zkušeností s výukou kurzu SPS (Struktury počítačových systémů) na Katedře řídicí techniky Elektrotechnické fakultě v Praze. Používá se v něm VHDL jako pomocný nástroj pro laboratorní experimenty s počítačovými systémy a paralelním chováním jejich obvodů — bez HDL jazyků se dnes již nedá rozumně pracovat. Studenti ale nebudou většinou profesionálně navrhovat hardware a potřebují jen jeho základy, ale i ty by měli znát na technické úrovni odpovídající vysoké škole. Při psaní dobrých VHDL programů je třeba občas pomyslet na strukturu vytvářeného zapojení a nelze jen mechanicky "bušit" jakési programové příkazy. V češtině existuje několik učebnic VHDL i webových stránek s jeho popisy, avšak často bývají příliš programátorské, soustředěné na samotný jazyk, a navíc postrádají vazby k vhodnému vývojovému prostředí, ve kterém by se daly návrhy obratem vyzkoušet. Organizace textu Výklad bude vycházet z příkladů cílených na FPGA vývojovou desku DE2 firmy Altera. Začneme od lehké majority ze tří, kterou použijeme nejen k úvodu do VHDL, ale současně i k návodu jak pracovat s VHDL v prostředí Altera Quartus II. Kvůli tomu doporučuji i pokročilejším čtenářům, aby si úvodní příklad udělali úplně celý. Další části pak budou věnované více jazyku VHDL.
Prerekvizity V úvodu do VHDL budeme přepokládat, že čtenáři mají instalované programovací prostředí Quartus II od firmy Altera, jehož bezplatnou verzi Quartus II Web Edition Software si lze stáhnout ze stránek firmy po registraci či z webu SPS. Umožní práci jen s některými FPGA, ale se všemi potřebnými v kurzu SPS, a nepovoluje dílčí překlady částí obvodů; vždy překládá vše, což u menších návrhů nevadí. Jinak nabízí stejnou funkčnost jako komerční verze. mají správně vytvořený projekt v Quartus II určený pro desku DE2, a to včetně přiřazení odpovídajících "Pin Assignments" desky DE2 ze souboru "DE2_pin_assignments.csv" ; ovládají práci ve vývojovém prostředí Quartus II na úrovni symbolického editoru.
Pokud někomu uvedené znalosti chybí, může je získat z materiálů na stránce předmětu SPS: https://moodle.dce.fel.cvut.cz/course/view.php?id=5 Rada: Text obsahuje krátké příklady. Úmyslně nepřikládám jejich zdrojové kódy. Nekopírujte si je z obrazovky. Prostudujte si vždy celý příklad a pak si ho vytvořte zpaměti od začátku — a pokud možno s minimem pohledů na text. Metodou "Copy-Paste" se ještě nikdo nic nenaučil ! 4
1. VHDL stylem "Dataflow" Popis stylem "dataflow", tedy pomocí toku dat, se hodí pro části popsané kombinačními obvody, v nichž závislost výstupů na vstupech lze popsat logickými operacemi.
1-1 Majorita ze tří — úvod do VHDL a prostředí Quartus II Zadání: Navrhněte logický obvod se třemi vstupy A, B a C a jedním výstupem Y, který bude v logické ´1´, pokud dva nebo tři vstupy budou v logické ´1´, jinak bude v logické ´0´. 1-1.a) Schéma obvodu v symbolickém editoru A
C
0
0
1
0
0
1
1
1
A.B + A.C + B.C
B
A
Majorita ze tří
B
Y
C Obrázek 1 - Majorita ze tří
Řešení: Výstup má být v logické ´1´ při libovolných dvou vstupech v ´1´. Ze tří vstupů lze vytvořit tři dvojice A-B, A-C a B-C. Pokud jejich členy spojíme v AND-termy (oba vstupy v dvojici jsou v ´1´) a ty sloučíme OR operacemi (aspoň jedna dvojice musí být v ´1´), pak dostaneme Y = (A and B) or (A and C) or (B and C). Stejný výsledek dá i SoP (Sum-of-Products) pokrytí Karnaughovy mapy. Navržený obvod lze vytvořit v grafickém editoru souborů *.bdf (Block Diagram/Schematic File) ze tří hradel AND a jednoho hradla OR.
1-1.b) Vytvoření VHDL kódu
Lze použít jakýkoliv textový editor. Výborně se hodí interní VHDL editor v Quartus II, který už od verze 11 nabízí zvýraznění syntaxe a automatické doplňování kódu po napsání prvních tří písmen. Vytvoříme nový soubor VHDL z hlavního menu Quartusu (File->New -> VHDL file) a uložíme ho (File->Save As) pod názvem vytvářeného obvodu — tím bude název v jeho bloku entity, viz dále. Zvolíme-li pro entitu název majorita, pak VHDL soubor musíme nazvat majorita.vhd. Poznámka: Ve VHDL se každý obvod (entita) ukládá do stejnojmenného souboru, jelikož překladač právě tohle předpokládá. o Alternativní možností je komerční editor Sigasi (http://www.sigasi.com). Jeho bezplatná verze nabízí vše pro menší soubory a pro větší se přepne na omezené možnosti. Obsahuje i trochu pokročilejší ediční funkce; jako třeba přejmenování proměnných ("Refactor") a zvýraznění chyb při psaní. V budově FEL na Karlově náměstí lze používat i jeho plnou verzi jako pomocný editor — Quartus II detekuje externě změněné soubory a nabídne jejich aktualizaci. Komentáře ve VHDL začínají dvojicí pomlček "- - " a končí s koncem řádku. Neexistuje sice možnost jak komentovat celé bloky, třeba označit každý řádek zvlášť, ale Quartus II i Sigasi dovedou vložit komentářové pomlčky na začátky všech řádek ve vybraných blocích a případně je zas odebrat. Raději nepoužívejte diakritiku, a to ani v komentářích. Překladače ji někdy vezmou, jindy ne. Spolehlivě rozumí jen čistému ASCII. Pozn. VHDL programy nebudete překládat jen v Quartusu, ale pro simulace i v ModelSim, a tak se hodí dodržet kompatibilitu pro oba překladače. VHDL nerozlišuje malá a velká písmena. Nicméně bývá dobrým zvykem psát proměnné a klíčová slova pořád stejně. I když "entity", "Entity", "ENTITY" i "EnTiTy" bere překladač jako stejné slovo, program bude mnohem přehlednější, budeme-li psát vždy "entity".
5
VHDL kód uvedeme napřed celý, poté ukážeme jeho použití v editoru schémat, což nám posléze umožní lépe vysvětlit jeho části: -- Majorita ze tri library ieee; use ieee.std_logic_1164.all;
----entity majorita is -port -( a, b, c : in std_logic; -y : out std_logic -); -end; --architecture dataflow of majorita is -begin -y <= (a AND b) OR (a AND c) OR (b AND c); -end; --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Program 1 - Majorita ze tří
1-1.c) Jak VHDL program použijeme v grafickém editoru schémat? Pro začlenění do schématu volíme následující postup. VHDL program napřed otestujeme, zda neobsahuje syntaktickou chybu (ikona nad T šipkou) v obrázku dole, či ho přeložíme; označíme majorita.vhd za hlavní entitu a spustíme buď částečný překlad ikonou pod C šipkou či úplný ikonou pod P šipkou. Symbol (tj. schematickou značku) pro majoritu vytvoříme buď: 1/ v okně "Project Navigator" přepnutém na záložku "Files" vybereme soubor majorita.vhd, pravou myší otevřeme jeho kontextové menu a volíme "Create Symbol File for Current File." (S-šipka); nebo 2/ při VHDL souboru otevřeném v editoru volíme z hlavního menu Quartusu: "File-> Create/Update ->Symbol Files for Current Files".
Nyní můžeme program vyzkoušet. Vytvoříme nový "Block Diagram/Schematic File" (File->New), a nazveme ho třeba jako test.bdf a otevřeme v něm "Symbol Tool" buď z lišty nástrojů editoru schémat (viz dole obr. a), nebo alternativně pravým dvojklikem myši kamkoliv na prázdné místo grafické plochy.
6
a) Vložení bloku přes Symbol Tool
b) Vygenerování vstupů a výstupů
V dialogovém okně "Symbol" se nám přidala knihovnu "Project", z níž vybereme entitu majorita. Vložíme ji do schématu. Připojíme k ní vstupy a výstupy pro její vyzkoušení, a to buď manuálně, nebo si můžeme ušetřit trochu práce, když pravou myší vyvoláme kontextové menu vložené schematické značky majorita (viz předchozí obr. b) a necháme si pro ni automaticky vygenerovat vstupy a výstupy volbou "Generate Pins for Symbol Ports". Vložené "pins" jen přejmenujeme podle "assignments" desky DE2. Nyní zvolíme test.bdf za hlavní entitu, přeložíme ji a nahrajeme do desky DE2. Přepínači SW vyzkoušíme, že výstup je v ´1´ jen tehdy, když nejméně dva SW jsou v pozici nahoru, tj. v ´1´. Nemáte-li desku, alternativní možností je využití c) Schéma po přejmenování podle definic desky DE2 v simulátoru, viz materiály na stránce kurzu. Překladači nezáleží na tom, zda jsme entitu vytvořili ve VHDL nebo v symbolickém editoru. Oba přístupy jsou v mnohých operacích skoro rovnocenné, ovšem ne v pracnosti, složitější obvody se rychleji navrhnou ve VHDL, kde se dají i snáze porovnávat verze programu než u schémat. 1-1.d) Vysvětlení VHDL majority: knihovny na řádku 2 až 3 Probírání strukturu kódu VHDL začneme od knihoven. -- Majorita ze tri
-- 1
library ieee; use ieee.std_logic_1164.all;
-- 2 -- 3
entity majorita is port ( a, b, c : in std_logic; y : out std_logic ); end; architecture dataflow of majorita is begin y <= (a AND b) OR (a AND c) OR (b AND c); end;
-------------
4 5 6 7 8 9 10 11 12 13 14 15
Příkaz "library" (řádek 2) aktivuje knihovnu, zde standardní ieee navrženou v "Institute of Electrical and Electronics Engineers (IEEE)". Na řádku 3 se z ní vybírá balíček ("package") deklarující standardní logiku "std_logic_1164" a z něho vezmeme vše (all). Pozn. Volba "all" je nejčastější, ale z knihovny lze též vybrat i jeden prvek, což se někdy hodí pro řešení konfliktů u složitějších projektů. Řádky 2 a 3 jsou v úvodu téměř každého VHDL programu napsaného pro standardní logické obvody. Můžete je mechanicky vkládat na začátek každého nového souboru, který vytvoříte v kurzu SPS. 7
Poznámka: Snadná práce s knihovnami představuje výhodu VHDL oproti Verilogu, který zde nabízí mnohem omezenější možnosti. 1-1.e) Vysvětlení VHDL majority: Blok entita — řádky 5 až 12 Návrh obvodu povinně obsahuje deklaraci bloku entity, v našem programu na řádku 5: entity majorita is kde entity je klíčové slovo a "majorita" představuje námi přiřazený název obvodu shodný se jménem VHDL souboru (majorita.vhd). Blok entity může obsahovat několik definic (ale nemusí mít ani jednu, může být i prázdný). Náš příklad má jedinou definici, a to "port", proměnných vstupů a výstupů. Na řádku 10 blok entity končí klíčovým slovem end; Zakončení lze napsat zkráceně, tak jako v našem příkladu, nebo případně za end zopakovat i klíčové slovo entity nebo také název, což se povoluje od verze VHDL-93 (používané v kurzu SPS). Konec našeho bloku entity může mít tedy některý z tvarů: entity majorita is entity majorita is entity majorita is entity majorita is -deklarace -deklarace -deklarace -deklarace end; end entity; end majorita; end entity majorita; Tabulka 1 - možnosti zakončení deklarace entity ve VHDL-93 a vyšším
Zkracování ukončujících "end" se povoluje i u některých dalších deklarací, například také u bloku architecture na rádcích 12 až 15, kde možno uplatnit stejný princip. Volba zakončení závisí jen na programátorovi. U kratších bloků se volí jen end, u delších deklarací bývá lepší pro zvýšení přehlednosti programu zopakovat klíčové slovo úvodní deklarace bloku, zde entity, nebo přidat i název bloku či obojí. Blok entity určuje vnější vzhled obvodu, čili jak obvod uvidíme po vložení jeho instance do schématu, viz ukázka použití v editoru schémat; kvůli tomu jsme ji uvedli před rozborem programu. VHDL blok entity se obecně chová úplně jinak než třídy známé z C++, Javy a C#, nicméně můžeme říct, že lze najít nepatrnou analogie v tom, že název uvedený v úvodu bloku entity odpovídá jménu třídy — s odvoláním na něj vytváříme instance třídy, ve VHDL obvodu. 1-1.f) Vysvětlení VHDL majority: Definice port — řádky 6 až 9 Definice port se může objevit jen uvnitř deklarace entity a nejvýše jen jednou. Specifikuje proměnné vstupů a výstupů, tedy jakési analogie prvků "public" tříd viditelných zvnějšku. Obsahuje seznamy proměnných spolu se seznamy jejich názvů (signal-names), datovými typy (signal-type) a módy (mode) určujícími druh vstupu či výstupu. Pozn. Všimněte si, že za posledním signal-type chybí středník — jeho napsání bývá častou syntaktickou chybou začátečníků. -- Majorita ze tri library ieee; use ieee.std_logic_1164.all; entity majorita is
port ( a, b, c : in std_logic; y : out std_logic ); end; architecture dataflow of majorita is begin y <= (a AND b) OR (a AND c) OR (b AND c); end;
Názvy proměnných a bloků: VHDL názvy začínají písmenem A-Z a pokračují písmeny a číslicemi 0-9. Malá a velká písmena se nerozlišují, ale doporučuje se psát názvy pořád stejně. Název nesmí být klíčovým slovem. Může obsahovat znaky podtrhávače _, avšak nikoliv na začátku nebo konci názvu, nebo dva podtrhávače za sebou. Povolené názvy: A1, A_1, A12_b7_Z Nepovolené názvy: A1_ (podtrhávač na konci), A__1 (dva podtrhávače), end (klíčové slovo), A$B (nepovolený znak $.).
•
VHDL od verze 93 dovoluje i rozšířené názvy ohraničené znaky \ \ — v těch lze použít i zakázané konstrukce. Například dovolené rozšířené názvy jsou \123.45\ , \END\ či \A$B\. Slouží pro speciální operace a kódy automaticky generované překladači. V běžných programech se z důvodu kompatibility doporučuje používat normální názvy. • V delších programech volíme názvy vždy výstižné, specifikující účel či použití. • Mód proměnné: Ve většině návrhů se používají jen módy in a out. Mód in označuje vstup, jehož hodnotu lze zvnitřku obvodu jen číst, ale nelze do něho zapisovat. Naproti tomu mód out specifikuje výstup, jemuž můžeme zvnitřku obvodu přiřadit hodnotu, ale nelze ho číst. • Pokud musíme hodnotu výstupu i číst, lze použít mód buffer, ovšem pro něj musí překladač vyhradit trochu obsažnější element, jelikož z výstupu vede drát zpět do vnitřku obvodu. • Posledním módem je typ inout, který dovoluje data jak číst tak do nich zapisovat. Vyžaduje ovšem složitý prvek obousměrného budiče, a tak se používá jedině Obrázek 2 - Realizace módů v obvodu výjimečně, například pro obousměrné směrnice jako třeba sběrnice I2C (na desce DE2 je přes ni připojený audio výstup). • •
V návrzích určených pro syntézu obvodů se doporučuje používat přednostně módy in a out. Pozn. O portech se zmíníme ještě v sekci "1-2.b) O definici "signal" na řádce 13" na str. 13. Datové typy: Pro vstupy a výstupy se používají typy odvozené od std_logic, což je proměnná výčtového typu obsahující 9 hodnot: '1', '0', 'X', 'Z','U', '-', 'L', 'H', 'W' vícehodnotové logiky zvané MVL-9 (Multi Value Logic 9). Pomocí ní lze dobře popsat i neurčitost při návrhu a simulacích obvodů. Datové typy odvozené od std_logic se při VHDL syntéze volí pro další proměnné spojené v toku dat se vstupy a výstupy, neboť překladače je mohou lépe syntetizovat. Unitialized
´U´
objeví se zpravidla jen při simulaci obvodu, kdy díky nezadané inicializaci není možné stanovit hodnotu výstupu
Don’t Care
´-´
v návrhu dosud nezadaná hodnota
Forcing 1
´1´
logická 1
Forcing 0
´0´
logická 0
Forcing Unknown
´X´
neznámá hodnota, při simulaci ji nelze stanovit
Weak 1, Weak 0, Weak unknown High Impedance
´H´,´L´, ´W´ ´Z´
vhodné pro obvody s otevřeným kolektorem, FPGA řady Cyclone je ale neobsahují, a tak u nich nelze použít odpojení výstupu, třetí stav vysoké impedance Tabulka 2 - Typ std_logic
•
Pozn. VHDL obsahuje i jednobitové datové typy "Boolean" a "Bit", oba opět realizované jako výčtové typy. Typ "Boolean" s hodnotami TRUE a FALSE lze použít jedině pro podmínky, viz dále. Typ "Bit" s hodnotami ´0´ a ´1´ se zase hodí jen pro generátory simulací či podobné pomocné výpočetní části. Striktně se nedoporučuje používat ho v programech pro syntézu obvodů, protože překladač pak nedetekuje některé obvodové chyby, jako například dva zdroje signálu ve zkratu. 9
Pozor —datové typy std_logic a bit nejsou převoditelné žádným přetypováním. •
Pořadí definic v bloku port je sice libovolné, ale určujeme tím pořadí vstupů a výstupů na schematické značky, a proto ji pro schéma musíme po každé změně definice port znovu vygenerovat pomocí již zmíněného "Create Symbol File for Current File". V definicích port lze použít buď seznam proměnných od sebe oddělených čárkami, tak jako v Program 1 na str. 6, nebo každou proměnnou psát na samostatný řádek, což dovoluje přidat k ní komentář jejího významu. Komentáře zde vynecháváme jen pro zkrácení délek řádek, ale v dobrém kódu by je měly být u každého vstupu a výstupu. entity majorita is port ( b : in std_logic; y : out std_logic; c : in std_logic; a : in std_logic ); end;
majorita b
y
c a inst
Program 2 - Pořadí vstupů a výstupů v entitě a ve schematické značce
1-1.g) Vysvětlení VHDL majority: Architektura — řádky 12 až 15 Blok architektury popisuje vlastní obvod. V programu majorita začíná na řádku 12, a to klíčovým slovem architecture, za nímž následuje jméno vytvářené architektury a název entity, pro niž je architektura určená. Poté následuje příkazový blok begin end; připomínající bloky v Pascalu. Blok entity se v některých publikacích připodobňuje k interface v Pascalu a blok architektury zase k vlastní implementaci. Lépe by se možná hodila jiná podobnost přirovnávající obvod ke krabičce s elektronickým přístrojem, třeba se zesilovačem. Entita pak představuje ovládací prvky vyvedené na povrch krabičky, jako třeba vstupy signálu z mikrofonu, kytary nebo přehrávače, zdířky pro připojení výstupů na reproduktory či sluchátka a voliče zesílení. Architektura se zde podobá vnitřnímu zapojení zesilovače, skrytému před vnějším pozorovatelem uvnitř krabičky. Lze měnit ovládací prvky na povrchu krabičky (tj. entitu) a přizpůsobit jim vnitřní zapojení (tj. architekturu), nebo naopak měnit jen vnitřní zapojení pro vylepšení funkce a plně zachovat ovládací prvky, či změnit oboje. -- Majorita ze tri library ieee; use ieee.std_logic_1164.all; entity majorita is port ( a, b, c : in std_logic; y : out std_logic ); end;
architecture dataflow of majorita is begin y <= (a AND b) OR (a AND c) OR (b AND c);
end;
----------------
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Název architektury dataflow byl zvolený podle stylu programu. Naši entitu majorita popisujeme architekturou ve stylu "dataflow", a tak jsme ji tak nazvali. Každý název architektury patří jen do jmenného prostoru identifikátorů své entity. Jinými slovy — u dalších entit můžeme jejich architektury opět nazvat dataflow, budou-li popsané tímto stylem. Pozn. Povinný název architektury slouží pro případy, kdy entita má několik různých architektur, tj. více bloků architecture pro jednu entitu. Víc různých architektur se hodí například tehdy, když potřebujeme jednu funkci obvodu pro jeho samotnou realizaci a další pro jeho urychlení při simulacích. Používání několika architektur, samozřejmě odlišených různými názvy, představuje už pokročilejší téma — v kurzu SPS vystačíte s jednou architekturou pro každou entitu.
10
Pro zakončení bloku architektury klíčovým slovem end platí podobné možnosti jako pro zakončení bloku entity. Za klíčovým slovem end možno zopakovat i klíčové slovo architecture i její název dataflow, podobně jako u zakončení bloku entity, viz Tabulka 1 na straně 8. 1-1.h) Vysvětlení VHDL majority: Příkazový blok— řádka 14 Vlastní implementace architektury zaujímá jedinou řádku 14. Obsahuje logický výraz a přiřazovací příkaz <= označovaný jako "concurrent assignment", tedy souběžné, resp. paralelní přiřazení. Všechna taková přiřazení se provádějí současně, bez ohledu na jejich pořadí. V našem jednoduchém příkladu máme však jen jedno, a tak ho více rozebereme v další části, kde si náš příklad rozšíříme o další výstup. Logický výraz obsahuje operátory AND a OR. VHDL jazyk definuje následující operátory se jmény odpovídajícími běžným názvům hradel, ale rozeznává pouze dvě priority. Nejvyšší má unární operátor negace NOT, zbylé operátory mají stejnou prioritu, závorky jsou ve výrazech nezbytností. Vyšší priorita Nižší priorita
not and or nand nor xor xnor Tabulka 3 - Logické operátory jazyka VHDL
Pokud bychom v našem výrazu y <= (a AND b) OR (a AND c) OR (b AND c);
vynechali závorky a napsali ho jako y <= a AND b OR a AND c OR b AND c;
pak by se ve skutečnosti realizovalo zapojení odpovídající výrazu y <= (((((a AND b) OR a) AND c ) OR b) AND c);
a schématu s úplně jinou funkcí:
a b c
y Obrázek 3 - Schéma odpovídající přiřazení y <= a AND b OR a AND c OR b AND c;
1-2 Rozšířená majorita ze tří — VHDL signály Zadání: Rozšiřte majoritu o další výstup Y1, který bude signalizovat, že jen jeden vstup je ve stavu logické ´1´ a ostatní jsou v ´0´. Řešení: Máme dvě možnosti. 1) Přidáme rovnici pro další výstup Y1, ten bude v ´1´ pouze při jednom vstupu v ´1´, tzn. za některé kombinace vstupů ABC = "100", "010" a "001". Podmínku můžeme vyjádřit logickou rovnicí: Y1 <= (A AND not B AND not C) or (not A AND B AND not C) OR (not A AND not B AND C); 2) Uplatníme skupinovou minimalizaci, tj. využijeme již stávající rovnice pro Y. Výstup Y je v ´1´ jen při dvou nebo třech vstupech v ´1´, ve zbývajících případech bude v ´0´, tedy při všech vstupech v ´0´ a právě při jednom vstupu v ´1´. Stačí tedy selektivně vybrat poslední podmínku, k čemuž můžeme použít OR-term všech vstupů a ten následně spojit AND s negací Y. Y1 <= not Y AND (A OR B OR C); Rovnice nám říká, výstup Y1 bude v ´1´ právě tehdy, pokud nebude splněna podmínka majority (not Y) a aspoň jeden vstup bude v ´1´, což spolehlivě vyloučí nechtěný případ, kdy všechny vstupy jsou v ´0´. 1-2.a) Postup rozšíření VHDL majority o další výstup V deklaraci entity přidáme pouze výstup Y1. -- Majorita ze tri s výstupem jednoho signálu library ieee;
11
-- 1 -- 2
---------
use ieee.std_logic_1164.all; entity majorita is port ( a, b, c : in std_logic; y, y1 : out std_logic ); end;
3 4 5 6 7 8 9 10
V architektuře nelze napsat přímo y1 <= not y AND (a OR b OR c); protože překladač by ohlásil chybu, že výstup y nelze číst, protože je módu out, viz část "1-1.f) Vysvětlení VHDL majority: Definice port — řádky 6 až 9" na straně 8. Museli bychom buď změnit mód výstupu y na buffer, čímž ale zbytečně ztížíme implementaci kódu. Lepší řešení nabízí možnost vložení pomocného vodiče; ten se ve VHDL nazývá signal. Pojmenujeme ho ytmp a bude stejného datového typu jako proměnná y, tedy datového typu std_logic. Definici signálu "signal ytmp : std_logic;" vložíme před příkazový blok begin end architektury Původní výraz y <= (a AND b) OR (a AND c) OR (b AND c); změníme. Jeho pravou stranu přiřadíme do signálu ytmp zápisem: ytmp <= (a AND b) OR (a AND c) OR (b AND c); a poté ytmp přiřadíme do y. Signál ytmp již smíme číst a lze ho použít ve výrazu pro y1. Celý program bude nyní vypadat takto: ------------
-- Majorita ze tri s výstupem jednoho signálu
library ieee; use ieee.std_logic_1164.all; entity majorita is port ( a, b, c : in std_logic; y, y1 : out std_logic ); end;
architecture dataflow of majorita is signal ytmp : std_logic; begin ytmp <= (a AND b) OR (a AND c) OR (b AND c); y <=ytmp; y1 <= not ytmp AND (a OR b OR c); end;
Program 3 - Majorita s pomocným výstupem jednoho signálu
12
1 2 3 4 5 6 7 8 9 10 11
-- 12 -- 13 -- 14 -- 15 -- 16 -- 17 -- 18
1-2.b) O definici "signal" na řádce 13 Signály jsou ve skutečnosti pojmenované vodiče neboli propojky. Můžeme je libovolněkrát číst dle potřeby. Čtení jejich hodnoty vlastně znamená, že z vodiče uděláme odbočku, čili na něj připojíme. Upravený program majorita vnáší změnu, která je zvýrazněna na obrázku dole. Zakroužkované hradlo už nemá k sobě připojené přímo Y, (viz dřívější obrázek na str. 5), ale pojmenovaný vodič ytmp (signal ytmp : std_logic;), připojený <= příkazem: ytmp<=(a AND b) OR (a AND c) OR (b AND c); Vodič ytmp vede na výstup y, a to příkazem "y <= ytmp;" . Hodnota vodiče (signálu) ytmp se připojuje i na vstup hradla not (invertoru), a to čtením hodnoty ytmp v y1 <= not ytmp AND (a OR b OR c); a ytmp
b
y Obrázek 4 - Upravená majorita
c
y1
Jelikož signály představují vodiče, můžeme jim hodnotu přiřadit pouze jednou v celém obvodu, jinými slovy lze je připojit jen na jeden zdroj signálu. Dvojí přiřazení by vlastně znamenalo, že vodič budíme ze dvou zdrojů najednou, například máme spojené výstupy dvou hradel do zkratu a ty se přetahují o výslednou hodnotu. architecture dataflow of majorita is signal ytmp : std_logic; begin ytmp <= (a AND b) OR (a AND c) OR (b AND c); y <=ytmp;
ytmp<= ytmp AND (a OR b OR c); -- chybné dvojí přiřazení do ytmp y1 <= ytmp; end;
Pokus o dvojí přiřazení, které je v předchozím špatném programu, vyvolá chybu překladače "Can't resolve multiple constant drivers for net "ytmp" at majorita.vhd". Co vlastně teď od překladače chceme, to nám přibližuje obrázek dole, v němž je hypotetická, neproveditelná spojka zvýrazněná červeně. a b c
ytmp
y y1
důsledek chybného dvojího přiřazení hodnot do signálu ytmp Obrázek 5 - Důsledek chybného dvojího přiřazení hodnot do signálu ytmp
Pozn. Spojení dvou výstupů se obecně povoluje pouze ve výjimečných případech, například u již zmíněných obvodů s otevřeným kolektorem, tzv. Wired-Or. DE2 deska s FPGA Cyclone neobsahuje speciality umožňující zkratovat výstupy, a tak podobné "krkolomnosti" jsou pro ni nepřeložitelné. Signály a definice vstupů a výstupů sekci v port I vstupy a výstupy definované v sekci port uvnitř bloku entity jsou ve skutečnosti pojmenované vodiče, ovšem vedoucí vně našeho obvodu. Z toho vyplývají všechna jejich omezení Vodič portu vstupu módu "in" smíme zvnitřku obvodu jen číst, protože bude buzený ze zdroje signálu přivedeného zvnějšku, tedy ze zapojení, které se připojí k našemu obvodu. 13
Naopak do vodiče portu výstupu módu "out" lze zvnitřku obvodu přiřadit hodnotu jenom jedenkrát, podobně jako do signálů. Jeho hodnota bude k dispozici pro jiná vnější zařízení. Jelikož z výstupu nevede spojka zpět do obvodu, nemůžeme ji zvnitřku obvodu číst. Do vodiče portu výstupu módu "buffer" se opět smí také zapsat jenom jedenkrát hodnota, ale jelikož od něho vede spojka dovnitř, smíme i číst jeho hodnotu. Pokud něco takového potřebujeme, pak je mnohem lepší použít pomocný signál tak, jako v příkladu upravené majority. Definicí buffer bychom jenom zbytečně komplikovali vnitřní implementaci obvodu. 1-2.c) Více o <= "concurrent assignment"
Všechny příkazy <= "concurrent assignment" se provádějí paralelně bez ohledu, kde a v jakém pořadí se v obvodu vyskytují. Například, pokud příkazy z předchozího programu proházíme, třeba úplně obráceně, pak program bude dál pracovat stejně jako prve bez jakéhokoliv snížení efektivity: y1 <= not ytmp AND (a OR b OR c); y <=ytmp; ytmp <= (a AND b) OR (a AND c) OR (b AND c); Příkazy definují zapojení a každé zapojení v obvodu pracuje současně se všemi ostatními. Zkuste si představit, že máte tři lampy či jiná elektrická zařízení. Všechna budou pracovat pořád stejně bez ohledu na to, v jakém pořadí je připojíte k přívodům přívodů elektrické energie. Důležité je jenom to, že jsou k němu připojené. Přesně takhle pracují příkazy <= "concurrent assignment". 1-2.d) Otestování upravené majority Chceme-li upravenou majoritu vyzkoušet ve schématu, musíme provést následující kroky: a) Napřed potřebujeme vygenerovat nový symbol obvodu majorita, protože jsme změnili definici port v deklaraci jeho entity. Postup se probíral v podkapitole "1-1.c) Jak VHDL program použijeme v grafickém editoru schémat?" na straně 6. b) Poté musíme aktualizovat stávající schéma obvodu v souboru test.bdf, což nejsnáze provedeme, když test.bdf otevřeme v grafickém editoru, pravou myší vyvoláme kontextové menu symbolu majority. Volíme "Update symbol or Block…". Aktualizaci schématu můžeme samozřejmě alternativně provést i tím, že starý symbol smažeme a na jeho místo vložíme nový.
Obrázek 6 - Aktualizace změněného symbolu
c) Do aktualizovaného schématu přidáme výstup na port y1 pojmenovaný třeba LEDR[1] d) Nakonec přeložíme test.bdf úplným překladem a nahrajeme výsledek do desky DE2.
14
majorita INPUT VCC INPUT VCC INPUT VCC
SW[0] SW[1] SW[2]
a b
y
OUTPUT
LEDR[0]
y1
OUTPUT
LEDR[1]
c
inst
Obrázek 7 - Výsledné schéma v test.bdf
1-2.e) Co když potřebujeme nerozšířenou verzi majority? Dvě různé majority s odlišnými výstupy v jednom projektu by si žádaly i odlišné názvy souborů a jejich entit. V našem případě jsme upřednostnili jen jeden název, tj. nechali si jen rozšířený obvod. Budeme-li někdy potřebovat obyčejnou majoritu, tj. bez rozšíření o výstup y1 aktivace právě jednoho signálu, tak y1 jednoduše nepoužijeme, tj. k ničemu ho nepřipojíme, viz obrázek dole: majorita SW[0] SW[1] SW[2]
INPUT VCC INPUT VCC INPUT VCC
a
y y1
b
OUTPUT
LEDR[0]
c
inst
Obrázek 8 - Rozšířená majorita s nezapojeným výstupem y1
Z protokolu překladače (Quartus okno "Compilation Report") plynou následující skutečnosti: Rozšířená majorita, Obrázek 7, se všemi zapojenými výstupy má složitost 2 LE, tedy dva logické elementy použitého FPGA. Zapojení, u něhož není zapojený výstup y1, Obrázek 8, má složitost 1 LE, tedy stejnou složitost jako původní nerozšířená majorita, viz Obrázek 1 na straně 5. Nezapojené rozšíření si překladač Quartus II automaticky vystřihl. Liší-li se obvody jen v přidaném rozšíření a zachovávají přitom svoji původní funkčnost, pak nemusíme nutně mít několik verzí pod různými názvy, ale stačí nám jen jeden univerzální obvod.
15
1-3 Dekodér pro ukazatel — příkazy with…select jazyka VHDL Zadání: Navrhněte dekodér pro lineární ukazatel hodnoty. V jeho tříbitovém výstupu bude tolik bitů v logické ´1´, kolik má adresa hodnotu, jak ukazuje pravdivostní tabulka vpravo.
Pravdivostní tabulka dekodéru Vstup adresy A1 Hodnota 0 0 1 0 2 1 3 1
A0 0 1 0 1
Výstupy Q2 Q1 0 0 0 0 1 0 1 1
Q0 0 1 1 1
Řešení: Můžeme snadno napsat tři logické rovnice, pro každý výstup jednu. Podobný postup není však univerzální a ani přehledný. Nehodil se pro delší ukazatele hodnoty, např. u dekodéru se čtyřbitovou adresou by měl patnáct výstupů a potřeboval by 15 rovnic. Hledáme jiné, mnohem přehlednější řešení. A1 A0 A1 A0 "000" "001" "011" "111"
Q2Q1Q0
"000" "001" "011"
0
"111"
3
1
Q2Q1Q0
2
Vhodné řešení nabízí přepínač. Poloha jeho jezdce je určena adresou a na kontakty jsou připojené požadované výstupní kombinace hodnot bitů. K přepínači existuje analogie v podobě multiplexoru propouštějícímu na výstup jen hodnotu ze vstupu vybraného adresou. Ve VHDL se multiplexor definuje příkazovou konstrukcí with...select. Řešení ve VHDL: Opět uvedeme napřed celý VHDL kód a pak rozebereme jeho zajímavé části odděleně. -- Dekoder pro linearni ukazatel library ieee; use ieee.std_logic_1164.all; entity dekoder2linearni is port ( A : in std_logic_vector(1 downto 0); Q : out std_logic_vector(2 downto 0) ); end;
----------
1 2 3 4 5 6 7 8 9
-- 10
architecture dataflow of dekoder2linearni is begin with A select Q <= "000" when "00", "001" when "01", "011" when "10", "111" when others; end;
---------
11 12 13 14 15 16 17 18
Program 4 - Dekodér pro lineární ukazatel
1-3.a) Příkaz with…select Vstup A i výstup Q jsou definované jako vektory, kterým v obvodech odpovídají sběrnice neboli skupiny číslovaných vodičů — A má dva vodiče A(1) a A(0); Q tři vodiče Q(2), Q(1) a Q(0). Začátek příkazu with A select specifikuje adresní vstup multiplexoru. Do Q se přiřazují hodnoty vybrané A. Jejich mapování určí konstanta za klíčovým slovem when rovná hodnotě vstupu adresy. Musí se vždy vyčerpat všechny možnosti — přepínač (multiplexor) pokaždé vrací nějakou hodnotu. Kvůli tomu poslední volba obsahuje klíčové slovo others, což znamená všechny dosud neuvedených možností. 16
1-3.b) Datový typ std_logic versus std_logic_vector Proměnné typu std_logic představují výčtový typ, jehož hodnoty byly uvedené na straně 9. Budeme-li mít definici signal t, x, y, z :std_logic;, pak do těchto proměnných můžeme přiřadit logický výraz nebo konstanty jeho výčtového typu, např. t<=´Z´; x<=´1´; y<=´0´; z <= x AND y; Konstanty ´1´ a ´0´ jsou v uvozovkách, nejde o čísla, ale o hodnoty z definice std_logic výčtového typu v knihovně ieee.std_logic_1164 seznamem hodnot, tj. analogicky k typům enum v Java či C#: type std_logic is ( 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-');
Datový typ std_logic_vector představuje naproti tomu jednorozměrné polem složené z prvků datového typu std_logic a standardní knihovna ieee.std_logic_1164 ho definuje jako type std_logic_vector is array (natural range<>) of s td_logic ;
kde
type je klíčové slovo definice typu a std_logic_vector název vytvořeného typu; natural znamená datový subtyp specifikující číslo typu integer omezené na rozsah 0 až ma-
ximální celé číslo definované v typu integer; (to můžeme zjistit atributem integer´HIGH, viz dále na str. 18 ). Knihovna ieee definuje 32bitvové integer, tj. v rozsahu od -2-31 do 231-1; range je klíčové slovo specifikující interval. Ve spojení s natural určuje interval popsaný celými čísly od 0 do maximálního čísla integer . range < > znamená nespecifikovaný rozsah, jeho hodnota bude doplněna při použití typu.
Budeme-li mít v architektuře například definice:
pak jim odpovídá následující uspořádání:
signal A, B, C: std_logic_vector (7 downto 0);
A, B a C: 7 6 5 4 3 2 1 0
signal Z: std_logic_vector (1 to 16)
Z:
preferované pořadí downto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Interval (range) u A, B a C směřuje od 7 do 0, tj. odshora dolů, tedy shodně s číslováním bitů v binárních číslech, což se upřednostňuje. Směr "to" se používá zpravidla jen pro interní proměnné. Konstanty typu std_logic jsou v ´ ´ apostrofech, zatímco u typu std_logic_vector jsou v " " uvozovkách, a to i v případě, když vektor má délku jen jeden člen. Vektorům totiž odpovídají řetězce. Dlouhé řetězce, pokud mají počet členů rovný přesně mocnině 2, můžeme zkráceně zapsat hexadecimálním řetězcem, v němž je znak X před první uvozovkou. Vše nejlépe přiblíží příklad použití výše uvedených proměnných: A<="10101100"; B<=X"5F"; C<=A AND B; Z(2 to 9)<=C; Z(14 to 15)<= C(4 downto 3); 7
6
5
4
3
2
1
0
A
1 0 0 0 1 1 0 0
B
0 1 0 1 1 1 1 1
7
7
C
6
6
5
5
4
4
3
3
2
2
1
1
A<="10001100";
0
B<=X"5F"; C<=A and B;
0
0 0 0 0 1 1 0 0
Z(14 to 15)<=C(4 downto 3);
Z(2 to 9)<=C; 1
2
3
4
5
6
7
8
9
10
11
12 13 14 15 16
Z 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 Obrázek 9 - Operace s std_logic_vector
POZOR, proměnné polí (vektorů) se musí pořád používat se směrem číslování určeným v jejich definici, tj. s downto nebo s to. Směr se nesmí nikdy obrátit. Napíšeme-li Z(15 downto 14), překladač ohlásí chybu obsahující text: "…range direction of object slice must be same as range direction of object…". 17
Z vektoru (pole) můžeme zvolit jeden člen nebo několik prvků, tedy podvektor. Napíšeme-li definice: signal A: std_logic_vector (7 downto 0); signal xv1, yv1: std_logic_vector (0 downto 0); signal x, y: std_logic;
pak můžeme použít následující příkazy: x <= A(0); A(1) <= '0'; yv1 <= "0"; xv1 <= A(2 downto 2);
A(0) vybere z pole prvek na indexu 0, ten bude datového typu std_logic. Inicializace prvku datového typu std_logic v poli A na indexu 1. Inicializace vektoru std_logic_vector , který má délku 1 člen. Z vektoru A se vybere vektor datového typu std_logic_vector o délce 1 člen; ten bude rovný prvku na indexu 2 vektoru A.
Pozn. Editor schémat (*.bdf) v Quartus II používá pro rozsahy polí stručné zápisy stylu Pascal. Schématu s popisy signálů A[2..0], Z[1..7] a A[1] odpovídají ve VHDL zápisy: A(2 downto 0), Z(1 to 7) a A(1). 1-3.c) Tvorba vlastních typů a atributy VHDL VHDL je typově velmi striktní. Definujeme-li si dva vlastní typy se shodnou strukturou, budou z hlediska překladače různé. Mějme například: type std_ l o g i c _ v e c t o r _ A i s a rra y ( 7 downto 0) of std_ logic ; type std_ l o g i c _ v e c t o r _ B i s a rra y ( 7 downto 0) of std_ logic; sign al s l v A1 , s l v A2 : s t d _ l o g i c _ v e c t o r _ A ; sign al s l vB 1 , sl vB 2 : s t d _ l o g i c _ v e c t o r _ B ;
Inicializace proběhne v pořádku; ta pracuje s členy vektorů a ty jsou shodného datového typu std_logic. slvA1 <= X"12"; slvB1 <= X"34"; -- OK, lze Pokud se pokusíme vzájemně přiřadit proměnné obou skupin, nepůjde to: slvB2<=slvA1; slvA2<=slvB1; -- Chyba při překladu - typy nesouhlasi U silně typových jazyků, mezi které patří i VHDL, se shoda struktury datového typu netestuje, důležitá jsou výhradně jména typů, a ta se liší. Existuje však možnost definovat si subtyp zužující originální typ. Přepíšeme-li výše uvedené definice na subtypy: subtype std_logic_vector_A is std_logic_vector (7 downto 0); subtype std_logic_vector_B is std_logic_vector (7 downto 0); signal sl vA1, sl vA2 : std_logic_vector_A ; signal sl vB1, sl vB2 : std_logic_vector_B ;
pak se příkazy předchozího programu přeloží bez chyby. Subtypy se pokládají za plně kompatibilní s výchozím datovým typem a všemi jeho subtypy: slvA1 <= X"12"; slvB1 <= X"34"; slvB2<=slvA1; slvA2<=slvB1; U neznámých typů se lze dotazovat na parametry jejich definic pomocí vlastností nazývaných ve VHDL atributy, které mají stejné užití jako "property" známé z tříd v Java či v C#, jen se neoddělují tečkou, ale apostrofem. Pro typy odvozené od std_logic_vector se hodí následující atributy: Příklad použití slvA1´left slvA1´right slvA1´low slvA1´high slvA1´length
Integer hodnota výsledku 7 0 0 7 8
Popis levá hodnota indexu rozsahu pravá hodnota indexu rozsahu nižní číselná hodnota indexu rozsahu vyšší číselná hodnota indexu rozsahu počet prvku
Tabulka 4 - Vybrané atributy
V definicích nových proměnných či v příkazech lze použít také atributy rozsahu: signal X : std_logic_vector(31 downto 0); signal novy1 : std_logic_vector(X'range); signal novy2 : std_logic_vector(X'reverse_range);
18
-- (31 downto 0) -- (0 to 31)
1-3.d) Dekodér pro lineární ukazatel s výstupy na jednotlivé vodiče Může se stát, že předchozí dekodér nevyhovuje a chceme jiný, kde vstupy a výstupy nejsou sběrnice, ale jednotlivé vodiče, jak ukazuje požadované schéma.
dekoder2linearniB INPUT VCC A1 INPUT VCC A0
SW[1] SW[0]
Můžeme využít velkou část předchozího kódu, ve kterém provedeme drobné změny, v programu dole vyznačené zvýrazněním:
Q2
OUTPUT
LEDR[2]
Q1
OUTPUT
LEDR[1]
Q0
OUTPUT
LEDR[0]
inst
Obrázek 10 - Dekodér pro lineární ukazatel s bitovými I/O
--Dekoder pro linearni ukazatel s oddelenymi vystupy
-- 1
library ieee; use ieee.std_logic_1164.all;
----------------------
entity dekoder2linearniB is port ( A1, A0 : in std_logic; Q2, Q1, Q0 : out std_logic ); end; architecture dataflow of dekoder2linearniB is signal A : std_logic_vector(1 downto 0); signal Q : std_logic_vector(2 downto 0); begin A <= A1 & A0; with A select Q <= "000" when "00", "001" when "01", "011" when "10", "111" when others; Q2<=Q(2); Q1<=Q(1); Q0 <= Q(0); end;
Program 5 - Dekodér pro ukazatel s oddělenými výstupy
Postup: a) Napřed jsme program uložili jako zcela nový soubor pod názvem dekoder2linearniB.vhd a blok entity přejmenovali na dekoder2linearniB, abychom mohli v projektu stále používat obě entity. b) Původní definice vstupů a výstupů v sekci port jsme přesunuli do architektury a udělali z nich signály, řádky 12 a 13, abychom mohli použít stávající kód, který se na ně odvolává. c) Do bloku port jsme vložili nové definice vstupů a výstupů jako jednotlivých signálů, řádky 6 a 7. d) Vektorový signál A jsme naplnili hodnotami vstupů A1 a A0 pomocí operátoru & jejich spojení do vektorové proměnné typu std_logic_vector, řádka 15. Lze samozřejmě použít i příkazy A(1)<=A1; A(0)<=A0; , ale operátor & je stručnějším zápisem. e) Výstupy žel musíme rozdělit po jednotlivých prvcích vektoru na řádce 21. Zde neexistuje žádná zkratka — operátor & nemůže stát jen na levé straně <= přiřazení. Pozn. V obvodech se spíše snažíme vodiče sjednocovat do vektorů (sběrnic), aby se s nimi lépe a rychleji pracovalo než z nich udělat mnoho samostatných drátků. f) Vygenerujeme-li symbolu dekoder2linearniB a vložíme-li ho do schématu, můžeme obvod vyzkoušet. Nevýhoda naší úpravy spočívá v duplicitě — kód pro lineární ukazatel máme nyní nakopírovaný do dvou entit. Bude-li potřeba opravit jednu kopii, nesmíme zapomenout provést změnu i ve druhé. V části o strukturálním popisu si ukážeme jinou metodu, a to jednodušší a mnohem bezpečnější.
19
1-4 Prioritní dekodér — VHDL příkaz when…else Zadání: Pro tři vstupy žádostí o přerušení procesoru Int3 Prioritní dekodér až Int1 navrhněte prioritní dekodér, který posílá na Výstupy Vstupy sběrnici číslo periférie žádající o přerušení. Při aktivním Int3 Int2 Int1 Q1 Q0 Číslo vstupu Int3 s nejvyšší prioritou bude na výstupu číslo 3, 1 1 1 3 ostatní vstupy se ignorují. Int1 má nejnižší prioritu 1 0 1 0 2 a posílá číslo 1, jsou-li zbývající vstupy v ´0´. Nežádá-li 1 0 0 0 1 1 nikdo se o přerušení, výstup bude číslo 0, viz zkrácená 0 0 0 0 0 0 pravdivostní tabulka. Řešení: Mohli bychom samozřejmě sestavit logické rovnice, což by lehce šlo u takhle malého obvodu, ale takový postup se nedá snadno rozšířit například na případ pro 15 vstupů se čtyřbitovým výstupem. Opět budeme hledat univerzálnější řešení. 0 Minule jsme si pomohli přepínačem, zde "00" Int1=0 0 Int2=0 se ho uplatníme také, ale nikoliv jako jeden 1 0 Int3=0 1 přepínač s mnoha pozicemi. Použijeme několik "01" "10" Q1Q0 1 dvoupólových přepínačů řazených do kaskády, "00" "11" čím přirozeně vytvoříme prioritu. 0 Int1=1 Bude-li sepnutý první přepínač Int3, pak "00" Int2=0 1 0 Int3=1 na výstup Q1 Q0 bude přivedena kombinace "01" 1 "11", tj. číslo 3, bez ohledu na pozice ostatních "10" Q1Q0 1 "11" "11" přepínačů. Nebude-li sepnutý Int3, teprve pak se uplatní další přepínače dle pořadí (priority). INT[1] INT[2] Ve schématu přepínače nahradíme multiINT[3] "00" 0 plexory dvou sběrnic o šířce dva bity; ty ozna0 1 0 čují dvojité čáry. Multiplexory budou řazené za "01" 1 Q[1..0] "10" sebou stejně jako přepínače a jejich jednobitové 1 "11" adresní vstupy budou vstupy přerušení INT[x], Obrázek 11 - Prioritní dekodér z multiplexorů a to v pořadí jejich požadované priority. Podobné konstrukci odpovídá ve VHDL podmíněné přiřazeni when …else. Po jeho vložení dostaneme kód: --Prioritni dekoder library ieee; use ieee.std_logic_1164.all;
-- 1 -- 2 -- 3
entity prioritni_dekoder is port ( INT : in std_logic_vector(3 downto 1); Q : out std_logic_vector(1 downto 0) ); end; architecture dataflow of prioritni_dekoder is begin Q<= "11" when INT(3)='1' else "10" when INT(2)='1' else "01" when INT(1)='1' else "00"; end;
------------
4 5 6 7 8 9 10 11 12 13 14
Program 6 - Prioritní dekodér
Chování příkazu when .. else, řádky 10 až 13, vyplývá z jeho implementace pomocí kaskády dvouvstupových multiplexorů dvoubitových sběrnic. Pokud bude nějaká podmínka splněna, její multiplexor se přepne a stav všech dalších multiplexorů ležící za ním, nemá již vliv na výsledek. Kvůli tomu 20
v dalších podmínkách ležících níže již nemusíme opakovat případné nesplnění předchozích podmínek. Bylo by zbytečné a matoucí mít na řádcích 10 až 13 v Program 6 něco takového jako: Q<= "11" when INT(3)='1' else "10" when INT(3)='0' and INT(2)='1' else "01" when INT(3)='0' and INT(2)='0' and INT(1)='1' else "00";
-----
10 11 12 13
Program 7 - Zbytečné podmínky v příkazu when...else
Pokud by někdo napsal příkaz takhle, nedopustil by se chyby, ale jen by zbytečnými testy opomíjel prioritní strukturu příkazu when…else, viz podtržené části. Např. hodnota na řádku 11 se použije jen při nesplnění podmínky na řádku 10; tak proč zde testovat INT(3)='0'? Kód ztrácí na přehlednosti vnucováním zbytečné otázky: "Kvůli čemu je tam tohle? — Aha, už vím; jen mňamka pro klávesu delete!" 1-4.a) Operátory porovnání a logické operátory ve VHDL Priorita Nejvyšší
Datový typ výsledku stejný jako operandy*)
Boolean (true, false) Nejnižší
Operátory not
=
stejný jako operandy*)
/= and
or
< nand
<= nor
> xor
>= xnor
*) Logické operátory jsou v ieee.std_logic_1164 definované jak pro typ std_logic , kdy vracejí výsledek typu std_logic, tak pro std_logic_vector kde dávají výsledek typu std_logic_vector, a také pro typ Boolean; zde je jejich výsledkem opět Boolean. Tabulka 5 - Porovnávací operátory a logické operátory
Porovnávací operátory mají obvyklou syntaxi, až méně běžný zápis /= pro nerovnost, a prioritně leží mezi operátorem not a ostatními logickými operátory. U složitějších výrazů se proto vyplatí používat závorky, aby se not aplikovalo správně. Datový typ Boolean, který vracejí porovnávací operátory, se dá použít jedině jako parametr podmínky; z příkazů s podmínkami zatím známe jen when..else. Opakované podmínky lze případně uložit do proměnné typu Boolean — ta nebude ale přiřaditelná do typů std_logic; zde nutno použít konstrukci when..else, viz řádky 11 a 17 dole. --Prioritni dekoder library ieee; use ieee.std_logic_1164.all; entity prioritni_dekoder is port ( INT : in std_logic_vector(3 downto 1); Q : out std_logic_vector(1 downto 0); NoInt : out std_logic ); end; architecture dataflow of prioritni_dekoder is signal jeInt3, zadnyInt :Boolean; begin jeInt3 <= INT(3)='1'; Q<= "11" when jeInt3 else "10" when INT(2)='1' else "01" when INT(1)='1' else "00"; zadnyInt <= not jeInt3 and INT(2)='0' and INT(1)='0'; NoInt <= '0' when zadnyInt else '1'; end;
-------------------
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Program 8 - Program pro demonstraci práce s proměnnými datového typu Boolean
Konstrukce na řádcích 16 a 17 je samozřejmě krajně krkolomná a slouží pouze jako demonstrativní ukázka práce s podmínkami. Dala by se samozřejmě nahradit: NoInt <= not INT(3) and not INT(2) and not INT(1);
Pozn. Jako podmínku nelze použít samotný logický výraz proměnných typu std_logic, např. napsat v programu nahoře na řádku 13 … when INT(2) else…; protože INT(2) není datového typu Boolean; ten nám vrátí až operátor porovnání. Musíme psát …when INT(2)='1' else…. 21
1-4.b) Pohled do nitra překladače přes RTL Viewer Tato podkapitola probírá mírně pokročilejší témata. Začátečníkům ji doporučuji přeskočit a vrátit se k ní později. Není zde uvedeno cokoliv, na co by se navazovalo v dalším textu. Prioritní dekodér jsme si začali tvořit jako schéma z multiplexorů, viz Obrázek 11 na straně 20. Bude ho překladač skutečně tak i implementovat? Poví nám to meziprodukty překladu; ty uvidíme až po přeložení top-level entity programu. Za tu vybereme buď 1/ soubor prioritni_dekoder.vhd, nebo 2/ schéma, do kterého jsme vložili prioritni_dekoder. Při způsobu 1/ najedeme myší na priority_dekoder v bloku entity, pravou myší vyvoláme kontextové menu, z něhož zvolíme Locate->Locate in RTL Viewer. Zvolili-li jsme cestu 2, vybereme ve schématu vloženou instanci a pravou myší vyvoláme její kontextové menu, kde provedeme stejnou volbu a poté ješte levou myší otevřeme zobrazený blok instance prioritni_dekoder.
Obrázek 12 - Vyvolání RTL Viewer z top-level entity
Ukáže se "RTL Viewer" (Register Transfer Logic Viewer) schéma ukazující, jak překladač pochopil náš program a jaký jeho model si sestavil po minimalizaci — opravdu použil kaskádu, ale jen ze dvou multiplexorů, ne tří jako na schématu Vytvořeno v Quartus II verze 13.0sp1 "Obrázek 11" na straně 20. Vyvoláváním nápověd u vodičů (najetím na ně myší), zjistíme, že schéma je zapojeno zjednodušeně. Na obrázku dole vlevo je překreslené RTL vnitřní schéma překladače a vpravo část hypotetického VHDL programu, který by mu logicky odpovídal. INT[3]
INT[2] 0
´1´ INT[1]
0
´0´
1
1
0
1bitový
´1´ 1 2bitový
Q[1]
Q[0]
architecture dataflow_RTL_Viewer of prioritni_dekoder is begin Q(1)<= '1' when INT(3)='1' else INT(2); Q(0)<= '1' when INT(3)='1' else '0' when INT(2)='1' else INT(1); end; Program 9 - Minimalizovaná kaskáda multiplexorů
Pokud se dobře podíváme na Obrázek 11 na straně 20, uvidíme, že překladač má pravdu. Levý multiplexor lze nahradit ´0´ & INT(1) — posílá "01" při INT1(1), jinak "00". Postřední multiplexor se zase dá zmenšit na jednobitový. Nicméně určitě můžeme označit za přehlednější úplný příkaz when…else v "Program 6 - Prioritní dekodér" na straně 20 než jeho super-minimalizovanou verzi v Program 9. I ve VHDL platí zásada, že programy píšeme především přehledně a srozumitelně. V drtivé většině případů můžeme dokonalé minimalizace nechat překladači — umí je lépe a taky rychleji než my . 22
1-5 Tříbitový prioritní inhibitor — cyklus for-generate Zadání: Navrhněte obvod s osmnácti vstupy A[17..0] a výstupy Q[17..0] propouštějící pouze nejvyšší vstup ve stavu ´1´ na jemu číselně odpovídající výstup, takže nejvýše jen jeden výstup je v ´1´ — kvůli tomu jsme obvod nazvali prioritní inhibitor. Pozn. Osmnáct vstupů a výstupů volíme kvůli desce DE2, která obsahuje osmnáct přepínačů SW a červených led diod LEDR. Kde bychom takový obvod použili? Představte si, že máte 18 přepínačů ovládajících nějaké funkce, přičemž se žádá, aby se najednou zapnul jen jeden přepínač. Uživatel může nechtěně, či úmyslně ("pokušitel jeden"), provést i několik voleb najednou. Pokud se za přepínače zapojí náš prioritní inhibitor, duplicitní zapnutí se potlačí podle priorit přepínačů a máme jistotu volby jen jedné funkce. Řešení: Příklad představuje prioritní úlohu a šel by řešit pomocí kaskády multiplexorů, stejně jako předchozí prioritní dekodér, tedy pomocí konstrukce when…else. Nedal by se ale lehce modifikovat pro jinou délku vektorů bez dlouhavého přepisování. Zkusíme najít elegantnější řešení. Napřed si napíšeme logickou tabulku pro dekodér se třemi vstupy a třemi výstupy, abychom zjistili, zda v ní najdeme možné zákonitosti: Úplná pravdivostní tabulka Vstupy Výstupy A2 A1 A0 Q2 Q1 Q0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 1 1 1 0 0 1 1 0 0 0 0 1 1 0 1 0 0 1 1 1 0 0 0 1 1 1 1 0 0
Z pravdivostní tabulky, a to především z její zkrácené verze, vidíme, že Výstup Q2 je evidentně kopií A2, čili Q2 = A2. Q1 bude v ´1´ jedině tehdy, pokud A2=´0´ a A1=´1´, což lze napsat rovnicí: Q1 = not A2 and A1. Výstup Q0 bude v ´1´ pouze při vstupech A2=´0´, A1=´0´ a A0=´1´, což zapíšeme logickou rovnicí Q0 = not A2 and not A1 and A0. Vidíme, že výstup Qi bude v ´1´ jedině tehdy, když všechny odpovídající vyšší bity A (tj. Aj pro j>i) jsou v ´0´. Označíme-li N počet bitů a názvem enable pole N proměnných, můžeme napsat rekurzivní vztah:
QN-1 := AN-1; enableN-1 := not AN-1; Qi := enable i+1 and Ai; enablei := enable i+1 and not Ai; pro i = N-2 až 0 Proč jsme místo jedné proměnné enable použili pole? Program budeme přepisovat do VHDL, kde signálům můžeme hodnotu přiřadit maximálně jen jednou, a tak jsme rekurzivní vztah záměrně vytvořili tak, aby respektoval podmínku jednoho zápisu do proměnné. Správnost našeho rekurzivního vztahu si můžete vyzkoušet třeba v Java metodě třídy: public void PriotritniInhibitor(boolean A[], boolean Q[]) { boolean[] enable = new boolean[Q.length]; if (A.length != Q.length || A.length < 2) throw new IllegalArgumentException("Obe pole musi mit stejnou delku>1"); Q[Q.length - 1] = A[Q.length - 1]; enable[Q.length-1] = ! (A[Q.length - 1]); for (int i = Q.length - 2; i >= 0; i--) { Q[i] = enable[i+1] && A[i]; enable[i] = enable[i+1] && !A[i]; } } } Program 10 - Java metoda pro vyzkoušení prioritního inhibitoru
23
//1 //2 //3 //4 //5 //6 //7 //8 //9 //10
Pole proměnných Java typu boolean nahradíme doporučeným typem std_logic_vector s číslování odshora dolů (downto). Cyklům for bude v hardwaru odpovídat opakování příkazů. Podobným způsobem se někdy i optimalizuje kód na rychlost; kratší cykly se nahradí rozbalením jejich příkazů. Například, mělo-li by pole Q z Java kódu Program 10 délku 3 prvky, tj. Q.length = 3, pak cyklus bude:
for (int i = 2; i >= 0; i--) { Q[i] = enable[i+1] && A[i]; enable[i] = enable[i+1] && !A[i]; }
//6 //7
a při jeho optimalizaci na rychlost by se nahradil sérií příkazů:
Výsledek má sice větší délku, ale ušetřilo se načítání a testování parametru cyklu a výpočty adres polí; jejich indexy jsou teď konstanty. Podobnou expanzi provede i VHDL pomocí generace. Výsledkem bude VHDL kód: -Prioritni inhinitor
-- 1
library ieee; use ieee.std_logic_1164.all; entity prioritni_inhibitor is port ( A : in std_logic_vector(17 downto 0); Q : out std_logic_vector(17 downto 0) ); end; architecture dataflow of prioritni_inhibitor is signal enable: std_logic_vector(Q'range); begin Q(Q'LENGTH-1)<=A(Q'LENGTH-1); enable(Q'LENGTH-1)<= not A(Q'LENGTH-1); cyklus : for i in Q'LENGTH-2 downto 0 generate Q(i)<=enable(i+1) and A(i); enable(i)<= enable(i+1) and not A(i); end generate; end;
Program 11 - Prioritní inhibitor pro 18 vstupů a výstupů
Generační cyklus začíná na řádku 12. Jeho obecná syntaxe je label : for <parameter> in generate end generate [label] ; kde label je povinné návěští oddělené dvojtečkou. Musíme ho zadat, i když ho nikde dál již nepoužijeme. Zvolili jsme název "cyklus", ale mohli i jiný, třeba "opakuj" apod. <parameter> představuje parametr cyklu, který bude nabývat hodnot v rozsahu ; udává rozsah hodnot; ten má stejný tvar jako rozsahy polí, je buď downto nebo to ; end generate [label] ; — cyklus se musí zakončit buď end generate nebo end generate label; Klíčové slovo generate je povinné, zde ho nelze vynechat; označuje blok příkazů řazených mezi "concurrent", tj. probíhajících paralelně. Sem patří přiřazení <= a konstrukce with..select , when..else, dále také for-generate (cykly lze vnořit) a lze použít i podmíněnou generaci if-generate a příkaz port map; ten probereme ve strukturálním stylu VHDL. Příkazy musí být vždy úplné; nelze tedy vkládat jen jejich dílčí části, jako třeba jednotlivé řádky podmínek ve "when..else" či přidávat členy do výrazů. Pozn. Příkaz for-generate lze použít jedině v architektuře, v entitě ne. Dále může i své mít lokální proměnné. Zájemci najdou podrobnější informace na webu ve VHDL referenčních příručkách. 24
prioritni_inhibitor SW[17..0]
INPUT
Q[17..0]
VCC A[17..1]
OUTPUT
LEDR[17..0]
inst
Obrázek 13 - Příklad použití prioritního inhibitoru
1-5.a) Jak vložit do výjimky do VHDL — příkazy generic a assert Java metoda Program 10 na str. 23, ve které jsme testovali rekurzivní vztah pro prioritní inhibitor, obsahovala příkaz "throw" pro metání výjimky v kódu. Ve VHDL programu jsme nic takového nepoužili; nemuseli jsme, vektory měly zatím pevné délky. Lze však mít i pole variabilní délky určené externím generic parametrem, který se zadává zvnějšku, a tak každá instance obvodu může jeho hodnotu mít odlišnou. Parametr definujeme v bloku entity, viz řádek 4 dole: - -Pr io ritn i in h in ito r lib r ary i ee e ; u s e i e e e. st d _l og ic _ 11 64 . a ll ;
-- 1 -- 2
entity prioritni_inhibitor is generic( N: natural := 18); -- delka vektoru > 1 port ( A : in std_lo gic_vector( N-1 downto 0); Q : out std_lo gic_vector( N-1 downto 0)); end; architecture dataflow of prioritni_inhibitor is signal enable: std_logic_vector( Q'range); begin assert N>1 report "Chybny parametr N . Pozadovano N> 1" severity failure; Q(Q'LENGTH-1)<= A(Q'LENGTH -1); enable(Q'LENGTH -1)<= not A(Q'LENGTH-1); cyklus : for i in Q'LENGTH-2 downto 0 generate Q(i)<=enable(i+1) and A(i); enable(i)<= enable(i+1 ) and not A(i); end generate; end;
Definice generic může obsahovat i několik různých parametrů: generic( name1: type1 := default_value1; name2: type2 := default_value2; ...
nameN: typeN := default_valueN );
Zadané parametry se v programu vždy chovají jako konstanty. Quartus II u nich požaduje výchozí hodnotu, která se přiřazuje pomocí := , nikoliv <= "concurrent assignment", protože nejde o propojení; všechny parametry se v době překladu nahradí hodnotami uvedenými v jejich instancích entity. Konstantu N jsme v našem programu použili u definic rozsahu vstupů a výstupů. Zbytek kódu jsme nemuseli přepisovat, jelikož jsme "prozíravě" použili atributy, což je obecně doporučovaný postup. Param eter Value Type N 18 Signed Integer prioritni_inhibitor SW[17..0]
INPUT VCC
A[n-1..0]
Q[n-1..0]
OUTPUT
LEDR[17..0]
inst
Obrázek 14 - Ukázka použití prioritního inhibitoru ve schématu
Pozn. 1/ Všimněte si, že stejně jako u bloku port se ani v generic nepíše ; za poslední prvkem seznamu. 2/ V editoru schémat lze zapnout/vypnout zobrazování hodnot parametrů u instancí v kontextovém menu editoru (pravou myší kamkoliv na volnou plochu a "Show->Show Parameter Assignments"). 25
Příkaz assert použitý v programu na řádcích 11 a 12 není přesnou analogií výjimky throw v Java, protože se nevyvolává při činnosti obvodu; to by ani nešlo. Při syntéze obvodu se vyhodnocuje už v době překladu a odpovídá stejnojmenným příkazům či metodám realizovaným různými formami i v Java, C# a C++. Ve VHDL má příkaz assert syntaxi: assert report <string> severity <severity_level>; kde je jakýkoliv výraz vracející datový typ Boolean; <string> označuje řetězec, který překladač vypíše uživateli, pokud nebude podmínka splněná, tj. při podmínce s hodnotou false; <severity_level> specifikuje požadovanou reakci překladače, ta je buď note, warning, error, failure, note — text se vypíše mezi informacemi; warning — vypíše se varováními a bude se pokračovat v sestavování obvodu; error — udává chybu zcela bránící v sestavení obvodu; failure — znamená inkonsistenci, nesmyslné zadání, které by nikdy nemělo nastat. Pozn. V příkazu assert lze vynechat část "severity <severity_level>", poté se pro <severity_level> automaticky zvolí výchozí hodnota error. Příklad vypsaných chyb při nedovolené hodnotě parametru ve schématu dole [Quartus II 13.0sp1]: Param eter Value Type N 1 Signed Integer prioritni_inhibitor SW[0]
INPUT VCC
A[n-1..0]
Q[n-1..0]
OUTPUT
LEDR[0]
inst
Error (10652): VHDL Assertion Statement at prioritni_inhibitor.vhd(11): assertion is false report "Parametr N musi byt vetsi jak 1" (FAILURE or ERROR) Error (12152): Can't elaborate user hierarchy "prioritni_inhibitor:inst" V hlášení se uvádí odkaz "prioritni_inhibitor.vhd(11)" na soubor prioritni_inhibitor.vhd řádek 11, kde se nachází příkaz assert, u něhož se nesplnila požadovaná podmínka. 1-5.b) Co programu ještě chybí? Poslední program se již blíží skutečným VHDL programům pro svou vysokou univerzalitu. Můžeme ho beze změny aplikovat na různé šířky sběrnic, ale přesto není příliš dobrý. Chybí zde především komentáře, jak jsme již uvedli zde úmyslně vynechané pro zkrácení řádek. Při nejmenším by se v dobrém programu měly pečlivě komentovat definice v bloku entity a hlavních proměnných, aby se nemusel luštit jejich význam. Hodí se i vkládat komentářové řádky před celky kódu specifikující, co se v nich vlastně provádí. Pozn. Hodně programátorů časem zjistí, že komentáře psali hlavně kvůli sobě, aby i dnes pořád věděli, co tehdy dávno napsali a proč právě takhle. První řádky programu by měly obsahovat více informací než název --Prioritni inhibitor; tohle víme už ze jména souboru! Hodilo by se přidat hlavně stručný popis, k čemu program vlastně slouží a případně i jméno jeho autora. Vhodná bývá i specifikace licence, pod níž je kód distribuován, aby ostatní věděli, jak ho mohou bez obav používat. U akademických programů se často volí "GNU General Public License". Jazyk – čeština může zůstat základem pro naše lokální programy v kurzu SPS. Chceme-li ale VHDL kód zpřístupnit více lidem, pak bývá lepší zvolit si angličtinu, a to nejen pro komentáře, ale také od ní odvozovat všechna jména proměnných. 26
2. VHDL stylem "Structural" VHDL umožňuje popsat návrhy i hierarchickou strukturou — můžeme obvod rozdělit na dílčí jednodušší entity, které samostatně odladíme, a poté popíšeme jejich propojení, podobně jako v editoru schémat. Musíme zde poznamenat, že pro menší celky nenabízí VHDL vždy takovou přehlednost jako grafická schéma. Jelikož opět použijeme jednoduché příklady, a tak výhody strukturálního popisu zde příliš nevyniknou. Nicméně hodí se ho znát; často ušetří hodně práce. Textový program přece jen: dovoluje lehčí porovnávání jednotlivých verzí než grafická schémata; s počtem prvků (entit) roste i množství potřebných propojek. Od určité složitosti se grafická schémata stávají nepřehledná a nepřinášejí už výhody, spíš naopak; VHDL program lze lépe procházet ladicími nástroji, jak ukazuje příloha o simulacích.
2-1 Použití prioritního inhibitoru ve VHDL V předchozí kapitole "1-5 Tříbitový prioritní inhibitor — cyklus for-generate" jsme si vytvořili program prioritní inhibitor. Umíme ho vložit do schématu a otestovat s deskou DE2. Lze ho však použít i samostatně, přímo z VHDL, což demonstrujeme na jednoduchém programu, který plně nahrazuje schéma "Obrázek 14" na straně 25: -- 1 --Test prioritniho inhibitoru -- 2 library ieee; use ieee.std_logic_1164.all; -- 3 entity test_inhibitoru is -- 4 port ( SW : in std_logic_vector(17 downto 0); -- 5 LEDR : out std_logic_vector(17 downto 0) ); -- 6 end; -- 7 architecture structural of test_inhibitoru is -- 8 component prioritni_inhibitor is -- 9 generic( N: natural := 18); -- 10 port ( A : in std_logic_vector(N-1 downto 0); -- 11 Q : out std_logic_vector(N-1 downto 0) ); -- 12 end component; -- 13 begin -- 14 inst_prioinhib : prioritni_inhibitor -- 15 generic map (N => 18) -- 16 port map ( A => SW, -- 17 Q => LEDR ); -- 18 end; Program 13 - Prioritní inhibitor ve strukturálním popisu
Řádky 1 až 7 obsahují běžný začátek VHDL programu — odkazují na knihovny (na řádku 2) a poté deklarují entitu nazvanou test_inhibitoru, a proto soubor uložíme pod názvem test_inhibitoru.vhd. V entitě definujeme v bloku port vstupy a výstupy, použijeme odkazy na desku DE2 coby přímé analogie vstupů a výstupů ze schématu "Obrázek 13" na straně 22. Na řádce 7 je pak začátek architektury, opět pojmenované dataflow. Řádky 8 až 12 se vyskytují na místě definic lokálních proměnných sekce architektury, kam jsme dříve vkládali i definice signálů. Nyní jsme tam uvedli blok entity přesně okopírovaný z řádek 3 až 7 "Program 12 - Prioritní inhibitor s generic" na straně 25. U okopírovaného bloku entity jsme jedině hradili původní entity klíčovým slovem component, které jsme vložili i za end ukončující bloku, zde ho nelze vynechat. Alternativně jsme mohli i uvést delší určení "end component prioritni_inhibitor". Vložení deklarace bloku component lze považovat za něco podobného vkládání prototypů známé funkcí z jazyka C nebo deklarací bloku interface PASCALu. 27
Pozn. Kód se nám přeloží, i kdybychom v bloku component uvedli neúplné deklarace ("incomplete type declarations") zavedené ve VHDL-93 pro složitější konstrukce, tedy vynechali default hodnotu u N v generic nebo opominuli rozsahy vektorů v port. Zmiňujeme se o tom, protože VHDL kódy nalezené na webu to občas používají, ale doporučuje se používat úplné deklarace — ty mají menší problémy s kompatibilitou.
component prioritni_inhibitor is generic( N: natural); port (A : in std_logic_vector; Q : out std_logic_vector ); end component;
-- 8 -- 9 -- 10 -- 11 -- 12
Program 14 - Nedoporučené použití neúplných definic v bloku "component"!
Elegantnější i pohodlnější možnost pro zkrácení kódu si ukážeme později na straně 30 v kapitole věnované tvorbě knihoven pomocí deklarace knihovních balíčků ("packages"). 2-1.a) Sekce port map a generic map Řádky 14 až 17 vytvoří instanci inhibitoru, tu pojmenujeme inst_prioinhib , dlouhý název se zde sice nepoužije, ale hodí se později v simulaci pomocí ModelSim. Mapování generic parametru N, které mu přiřadí hodnotu, a dále zapojení vstupů A a výstupů Q z define port lze provést dvěma způsoby: a) Jmenné asociace ("keyword notation") mají syntaxi: POZOR - všimněte si, že Členy jsou od sebe oddělené čárkami, nikoliv středníky — jde o seznam asociací.
generic map( generic_name1 => value1, generic_name2 => value2, ... Za závorkou není ; — mapa pokračuje. generic_nameN => valueN ) Zde jde opět o seznam s členy oddělenými od port map ( port_name1 => signal_name1, sebe čárkami. port_name2 => signal_name2, ... Až zde se za ) vyskytuje středník— konec mapy. port_nameN => signal_nameN ); Jelikož jde o asociativní seznamy, můžeme jejich členy uvést v libovolném pořadí, ale pro přehlednost se doporučuje zachovávat ho. Označení generic_nameX udává jméno parametru zcela shodné s jeho deklarací v sekci component generic a hodnota valueX specifikuje jemu přiřazenou hodnotu ve vytvářené instanci. Použijeme-li místo hodnoty výraz, pak jeho výsledek musí být známý v době překladu — jde o konstantu. Stejně tak port_nameX udává název vstupu nebo výstupu, a to opět, shodný s deklarací v component port a signal_name1 určuje, k jakému signálu bude připojen. Pokud se jedná o vstup, lze jeho hodnotu specifikovat výrazem, který nemusí mít konstantní hodnotu v době překladu, na rozdíl od generických parametrů. Výstup musí být samozřejmě vždy mapován na signál. Pozn. Použití výrazů v mapování port map dovolí Quartus, žel překladač v ModelSimu je zpravidla odmítne z implementačních důvodů, kvůli tomu bývá lepší výrazy napřed přiřadit do signálů a teprve ty mapovat na vstupy. b) Poziční asociace ("positional notation") mají syntax: generic map( value1, value2, ...
port map (
valueN ) signal_name1, signal_name2, ...
signal_nameN ); Hodnoty se v pozičních asociacích musí pochopitelně objevit přesně v pořadí deklarací jednotlivých parametrů, vstupů a výstupům v bloku component. Pro případné použití výrazů zde platí pravidla uvedená v odrážce "a) Jmenné asociace". Rozhodnutí zda použit jmenných či poziční asociace závisí plně na programátorovi. Pro jednodušší komponenty lze volit méně upovídané poziční asociace, zejména v případech, kdy deklarace použitých 28
component jsou vložené do entity, a tak si z nich lze snadno přečíst, čemu hodnoty vlastně přiřazujeme. U složitějších komponent a u komponent definovaných v externích knihovnách se vyplatí napsat mnohem přehlednější jmenné asociace, které navíc nabízejí vyšší imunitu vůči změnám v originálních entitách. 2-1.c) Sekce map a schéma Program 13 na str. 27 odpovídá schématu "Obrázek 13" na straně 22. Můžeme ho přeložit jako "top-level entity" a nahrát do desky DE2. Opačně lze i ze schématu vygenerovat VHDL soubor. Uložíme schéma "Obrázek 13" jako "test_inhibitoru2.bdf", otevřeme ho a z hlavního menu Quartusu volíme "File-> Create/Update -> Create HDL Design File from Current File" a poté v dialogu zadáme VHDL.
Obrázek 15 - Příkaz "Create HDL Design File"
Ve složce projektu se nám vygeneroval soubor "test_inhibitoru2.vhd", který se však automaticky nepřidal do projektu. Jeho entita má název test_inhibitoru2, tedy stejný jako entita vytvořená ze schématu "test_inhibitoru2.bdf". V projektu lze mít buď "test_inhibitoru2.vhd" nebo "test_inhibitoru2.bdf", nikoliv oba soubory, aby nám překladač nehlásil chybu duplicity. Odstraníme tedy "test_inhibitoru2.bdf" (okno Project Navigator záložka Files -> Remove File from Project") a můžeme vložit "test_inhibitoru2.vhd" (např. hlavní menu Quartus:Project->Add Remove Files in Project…, najdeme soubor […], dáme [Add] a [OK]): -- Copyright (C) 1991-2013 Altera Corporation… LIBRARY ieee; USE ieee.std_logic_1164.all; LIBRARY work; ENTITY test_inhibitoru2 IS PORT( SW : IN STD_LOGIC_VECTOR(17 DOWNTO 0); LEDR : OUT STD_LOGIC_VECTOR(17 DOWNTO 0) ); END test_inhibitoru2; ARCHITECTURE bdf_type OF test_inhibitoru2 IS COMPONENT prioritni_inhibitor GENERIC (N : INTEGER); PORT( A : IN STD_LOGIC_VECTOR(17 DOWNTO 0); Q : OUT STD_LOGIC_VECTOR(17 DOWNTO 0) ); END COMPONENT; BEGIN b2v_inst : prioritni_inhibitor GENERIC MAP(N => 18) PORT MAP( A => SW, Q => LEDR); END bdf_type;
-------------------
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Program 15 - Automaticky generovaný VHDL kód
Vygeneroval se nám stejný kód; zde ho jen uvádíme zhuštěněji formátovaný a se zkrácenými úvodními komentáři. Na řádce 2 má jen jeden nový prvek oproti našemu ručně udělanému programu — odkaz na "LIBRARY work;", tj. případné knihovny v balíčcích ("packages") definované ve složce projektu; ty zatím nemáme; povíme si o nich dále. Tvorba strukturálního VHDL popisu ze schémat může u složitějších obvodů ztrácet přehlednost díky pomocným proměnných, které si překladač někdy vytváří. Hodí se však pro začátky — pomůže nám pro začátek jako šablona pro názornější manuální sestavení.
29
2-1.c) Mapování vstupů a výstupů pro desku DE2 Pro Program 13 na straně 27 můžeme vygenerovat symbol, stejně jako pro každou jinou entitu, a vložit ho do schématu, tedy mít další vrstvu. Vůbec nevadí skutečnost, že jsme vstupy a výstupy u entity "test_inhibitoru" pojmenovali shodně s přiřazením na vývojové desce DE2 ("Assignments"). Naopak, volbou "Generate Pins for Symbol Ports", již dříve popsanou, rychle přiřadíme správně popsané vstupy a výstupy, a to prakticky bez práce. test_inhibitoru
SW[17..0]
INPUT VCC SW[17..0]
LEDR[17..0]
OUTPUT
LEDR[17..0]
inst2
Obrázek 16 - Test inhibitoru 2 vložený do schématu
Pozn. Zde raději připomeneme, že mapování vstupů a výstupů na FPGA "assignments" (desky DE2) se provádí jedině pro vstupy a výstupy uvedené na úrovni "top-level entity", tedy jen pro zvýrazněné prvky v obr. nahoře . U všech vložených entit se názvy berou jen jako jejich lokální proměnné.
2-2 Rozšíření prioritního inhibitoru o enable Zadání: Přidejte k prioritnímu inhibitoru vstup en (enable), který se při en=´0´ uvede maximálně rychle do ´0´ na všechny výstupy, zatímco při stavu en=´1´, bude inhibitor normálně pracovat. Řešení: Úloha by šla vyřešit použitím inhibitoru s N+1 vstupy a na jeho nejvyšší vstup s indexem A[N] připojit negovaný en, takže při en=´0´ by A[N]=´1´ a výstupy Q[N-1] až Q[0] by přešly do ´0´. Nicméně náš programátorský úmysl bude jasnější, pokud použijeme prioritní inhibitor s N vstupy a za něj připojíme multiplexor. Jako předlohu ("template") použijeme "Program 13 - Prioritní inhibitor ve strukturálním popisu", který upravíme přejmenováním a doplněním deklarace en. --Prioritni inhibitor s enable vstupem library ieee; use ieee.std_logic_1164.all; entity priroritni_inhibitor_enable is generic( N: natural := 18); port ( A : in std_logic_vector(N-1 downto 0); en : in std_logic; Q : out std_logic_vector(N-1 downto 0) ); end; architecture structural of priroritni_inhibitor_enable is component prioritni_inhibitor is generic( N: natural := 18); port ( A : in std_logic_vector(N-1 downto 0); Q : out std_logic_vector(N-1 downto 0) ); end component; signal Qtmp : std_logic_vector(Q´range); begin inst_prioinhib : prioritni_inhibitor generic map (N => N) port map ( A => A, Q => Qtmp ); Q <= Qtmp when en='1' else (others=>'0'); end;
Novou entitu jsme pojmenovali "priroritni_inhibitor_enable " a uložili do "priroritni_inhibitor_enable.vhd". Na řádku 6 jsme přidali definici nového vstupu en. Pro vstup zvnějšku jsme použili A (řádek 5), pro výstupy opět Q (řádek 7) a pro generický parametr N (řádek 4). Záměrně jsme napsali názvy shodné s komponentou prioritni_inhibitor, abychom mohli demonstrovat skutečnost, zvýrazněnou i barvami pozadí, že 30
proměnné z hlavičky vložené komponenty (řádek 10 až 14) jsou dostupné jedině u mapování její instance (řádek 17 až 19), a to na levé straně členů => asociativního listu, jinde se na ně nedá odkázat. Pokud uvedeme v generic map nebo port map asociaci XX=>YY, pak její levý člen XX se automaticky hledá mezi jmény komponenty, kterou právě mapujeme, tj. mezi jejími generickými parametry, či mezi vstupy a výstupy. Pravý člen asociace YY se bude hledat mezi proměnnými definovanými v našem obvodu v entitě a v architektuře. Napíšeme-li asociaci N=>N, pak levé N bude vztaženo k N v sekci generic map na řádku 18 a pravé N se vezme z generic na řádku 4. Řádek 20 obsahuje příkaz pro vytvoření multiplexoru. Příkaz when…else obecně vede na kaskádu multiplexorů při více podmínkách, tj. máme-li více řádků s when…else. Pro jednu jednoduchou podmínku (en='1') je úplně jedno, zda použijeme příkaz Q <= Qtmp when en='1' else (others=>'0'); nebo delší konstrukci: with en select Q<= Qtmp when '1', (others=>'0') when others; — výsledkem bude vždy jeden multiplexor. Upřednostnili jsme pochopitelně kratší zápis when…else. 2-2.a) Inicializace proměnných vektorů asociací others=> Klíčové slovo others znamená veškeré nepoužité hodnoty. Kromě příkazů, viz nahoře, ho lze užít i pro vytváření asociačních listů inicializujících proměnné, ovšem jedině za předpokladu, že bude přesně známa délka výsledku už v době překladu, tj. z našeho kódu půjde jednoznačně odvodit velikost proměnné, do níž zapisujeme. Na levé straně asociace inicializací stojí určení indexů v poli s možnými směry směry jak to tak downto a na pravé straně pak požadovaná hodnota jeho členu či členů. Možnosti nejlépe přiblíží následující demo-příklad: library ieee;use ieee.std_logic_1164.all; entity asociace is generic(N:natural:=8); port ( X1, X2, X3, X4, X5, X6, X7 : out std_logic_vector(N-1 downto 0) ); end entity; architecture structural of asociace is begin -- Pro N = 8 Pro N = 12 X1 <= (others=>'0'); -- X1="00000000" X1="000000000000" X2 <= (others=>'1'); -- X2="11111111" X2="111111111111" X3 <= (others=>'Z'); -- X3="ZZZZZZZZ" X3="ZZZZZZZZZZZZ" X4 <= (0=>'0', others=>'1'); -- X4="11111110" X4="111111111110" X5 <= (N-1=>'0', others=>'1'); -- X5="01111111" X5="011111111111" X6 <= (N-1=>'0', 1=>'0', others=>'1'); -- X6="01111101" X6="011111111101" X7 <= (N-1 downto N-3=>'1', 1 to 3=>'1', others=>'0'); -- X7="11101110" X7="111000001110" end; Program 17 - Demonstrace použití others pro inicializaci
Všimněte si, že hodnota u inicializační asociace se specifikuje shodně s datovým typem prvků pole, i když výsledek ovlivní více členů; jde o asociace na indexované prvky pole. Pole std_logic_vector má členy výčtového datového typu std_logic, a tak píšeme pravé strany asociací v ´ ´ apostrofech. Při inicializace X7 jsme v rozsazích použili jak směr downto tak i to , což zde můžeme, protože definujeme asociaci pro vytvoření inicializační konstanty, až její výsledek se přiřadí vektoru. Kdybychom chtěli X7 dát stejnou hodnotu bez asociativního listu, měli bychom více <= příkazů: X7(N-1 downto N-3) <= "111"; X7(N-4 downto 4) <=(others=>'0'); X7(3 downto 1) <="111"; X7(0)<='0'; Aplikaci others jsme se v nich stejně nevyhnuli — prostřední část vektoru X7(N-4 downto 4) má proměnlivou délku a bez others to tady nejde. V příkazech jsme již museli dodržovat směr downto určený v definicí vektoru. Pokus o použití směru to (např. X7(1 to 3) <="111"; ) by samozřejmě vyvolal chybové hlášení obsahující text: "…range direction of object slice must be same as range direction of object". 31
2-3 Vytvoření vlastní knihovny ("package") V kapitole "1-3.d) Dekodér pro lineární ukazatel s výstupy na jednotlivé " na straně 19 jsme slíbili, že modifikaci dekodéru vytvoříme elegantněji, bez opakování kódu. Samozřejmě můžeme postupovat stejným stylem jako v předchozí kapitole "2-2 Rozšíření prioritního inhibitoru o enable", tj. vložit hlavičku component a poté provést její mapování. Nicméně neustálé vkládání deklarací komponent před každým jejich použitím je, jak se to lidově říká, "votravné". Vždyť pro podobné situace existuje v jazyce C direktiva preprocesoru "#include" a v C# se zase používá příkaz using. V Java máme možnost odkázat na vytvořený "package". Stejný koncept se používá i ve VHDL. Vytvoříme package obsahující potřebné deklarace, na který jen odkazujeme: -- Knihovna kodu vytvorenych v uvodu do VHDL library ieee; use ieee.std_logic_1164.all;
-- 1 -- 2 -- 3
package uvod_knihovna is
-- 4 -- 5
component majorita is port ( a, b, c : in std_logic; y, y1 : out std_logic ); end component; component dekoder2linearni is port ( A : in std_logic_vector(1 downto 0); Q : out std_logic_vector(2 downto 0) ); end component; component prioritni_dekoder is port ( INT : in std_logic_vector(3 downto 1); Q : out std_logic_vector(1 downto 0) ); end component; component prioritni_inhibitor is generic( N: natural := 18); port ( A : in std_logic_vector(N-1 downto 0); Q : out std_logic_vector(N-1 downto 0) ); end component; component priroritni_inhibitor_enable is generic(N: natural := 18); port ( A : in std_logic_vector(N-1 downto 0); en : std_logic; Q : out std_logic_vector(N-1 downto 0) ); end component;
-- 34 Program 18 - Knihovna kódů vytvořených v úvodu do VHDL
Na řádku 2 odkazujeme na standardní knihovny, neboť v nich se nacházejí definice datových typů std_logic a std_logic_vector, které používáme v deklaracích komponent. Na řádku 4 definujeme náš název knihovny "uvod_knihovna" za klíčovým slovem package. Poté následují deklarace komponent. Vložili jsme sem všechny dosud vytvořené. Jejich definice zůstávají ve stejnojmenných souborech, takže je překladač snadno najde. Na řádku 34 končí deklarace knihovna klíčovým slovem end, ve VHDL-93 a vyšším lze za něj přidat i klíčové slovo package a případně i název knihovny uvod_knihovna, podobně jako u entity, viz "Tabulka 1 - možnosti zakončení deklarace entity" na str. 8. Náš balíček obsahuje pouze hlavičky komponent. Alternativně lze do něho přidat: 32
definice datových typů a subtypů pro použití v našich programech. Baliček představuje ostatně jedinou možnost jak naše vlastní typy a subtypy používat už v bloku entity; definice konstant; pro VHDL psané stylem "behavioral", který úvod nezahrnuje, a pro testovací části kódu nazývané "testbench" může knihovna obsahovat i deklarace funkcí, procedur a jiných prvků, ale pak v ní musí existovat i další blok package body … end , kde jsou tyto prvky definované. Zájemce o podobné speciality převyšující rámec kurzu možno odkázat na webové stránky, hledání "vhdl package". 2-3.a) Dekodér pro lineární ukazatel s výstupy na jednotlivé bity pomocí knihovny
Nyní můžeme lehce vytvořit modifikaci dekodéru pro lineární ukazatel s výstupy na jednotlivé bity ze strany 19, neboť už nemusíme vkládat deklaraci použité komponenty — ta je obsažena v naší knihovně. --Dekoder pro linearni ukazatel s nevektorovymi I/O library ieee; use ieee.std_logic_1164.all; library work; use work.uvod_knihovna.all;
-- 1 -- 2 -- 3 -- 4
entity dekoder2linearniC is port ( A1, A0 : in std_logic; Q2, Q1, Q0 : out std_logic ); end;
-----
5 6 7 8
-- 9
architecture structural of dekoder2linearniC is signal Q : std_logic_vector(2 downto 0); begin inst_dek2lin : dekoder2linearni port map (A=> A1 & A0, Q=>Q); Q2<=Q(2); Q1<=Q(1); Q0 <= Q(0); end;
--------
10 11 12 13 14 15 16
Program 19 - Dekodér pro lineární ukazatel s výstupy na jednotlivé bity pomocí knihovny
Na řádku 3 vkládáme odkaz na knihovnu; která se nachází v projektu; na ten odkazuje určení work. Syntaxe zápisu je podobná jako u standardní knihovny na řádku 2. Řádek 5 až 8 deklaruje vstupy a výstupy entity dekoder2linearniC. Řádek 13 a 14 vytvoří instanci dekodéru dekoder2linearni nazvanou inst_dek2lin. Pro mapování vstupů můžeme v Quartus použít výraz "A1 & A0" sjednocení bitových signálů do vektoru, zatímco výstupy je nutné opět rozepsat po jednotlivých bitech přes pomocnou proměnnou.
Kód je nyní dokonce kratší než původní Program 5 na straně 19 a navíc i zachovává zásadu sdílení kódu.
2-4 Zapojení prioritního dekodéru a lineárním ukazatelem Zadání: Vytvořte ve VHDL obvod skládající se z prioritního dekodéru přerušení a indikátoru výstupního čísla pomocí dekodéru pro lineární displej, dle schématu: prioritni_dekoder
dekorer2linearni
NOT
KEY [3..1]
INPUT VCC
Qtmp[1..0] INT[3..1]
Q[1..0]
A[1..0]
Q[2..0]
inst2 inst
inst1
OUTPUT
LEDG[2..0]
Obrázek 17 - Prioritní dekodér s lineárním ukazatelem
V zapojení si všimněte pojmenovaného vodiče, zvýrazněného červeně — pojmenování lze provést v editoru přes kontextové menu vodiče volbou "Properties". Hodilo by se nám, kdybychom generovali VHDL 33
kód ze schématu, což si můžete zkusit za domácí úkol, pokud chcete — překladač pak vytvoří pomocný signál stejného jména. Nicméně kód není složitý, lze ho lehce napsat přímo ve VHDL: --Prioritni dekoder s linearnim ukazatelem -- 1 library ieee; use ieee.std_logic_1164.all; -- 2 library work; use work.uvod_knihovna.all; -- 3 entity prioritni2linearni is -- 4 port ( KEY : in std_logic_vector(3 downto 1); -- 5 LEDG : out std_logic_vector(2 downto 0) ); -- 6 end; -- 7 architecture stuctural of prioritni2linearni is -- 8 signal Qtmp : std_logic_vector(1 downto 0); -- 9 signal nkey : std_logic_vector(3 downto 1); -- 10 begin -- 11 nkey<= not KEY; -- 12 inst_priodek : prioritni_dekoder -- 13 port map (INT=> nkey, Q=>Qtmp); -- 14 inst_dek2lin : dekoder2linearni -- 15 port map (A=>Qtmp, Q=>LEDG); -- 16 end; -- 17 Program 20 - Prioritní dekodér s výstupem na lineární ukazatel
Řádky 2 a 3 deklarují knihovny a můžeme je beze změny okopírovat z předchozího kódu v Program 19 na straně 33. Řádek 4 až 7 obsahuje entitu, v níž zadáme název našeho obvodu " prioritni2linearni " a poté běžným způsobem definujeme vstupy a výstupy, které připojíme na desku DE2. Řádek 8 a 9 zahajuje architekturu. V její hlavičce definujeme pomocný signál Qtmp pro propojení obou obvodů a signál nkey. Řádek 12 do signálu nkey připojíme negaci vstupů KEY; nestisknuté KEY dávají totiž ´1´. Signál použijeme pro mapování port map bez použití výrazu — v příloze plánujeme totiž otestovat kód v prostředí ModelSim, kde překladač nebere nekonstantní výrazy u mapování instancí. Řádek 13 a 14 obsahuje vytvoření instance prioritního dekodéru. Na jeho vstup INT mapujeme negaci signál nkey. Výstupy Q vyvedeme na pomocný vodič Qtmp — bez něho nelze propojení provést. Na vstupy, výstupy a parametry instancí odvolávat jen uvnitř jejich mapování (příkazy port map a generic map). Pozn. Ve VHDL nelze odkazovat na prvky instancí zápisy v objektovém stylu, tedy např. psát něco jako " inst.Q" apod. Řádek 15 a 16 mapuje druhou instanci — dekodér na lineární displej. Na jeho vstup A připojíme vodič Qtmp a na výstupy Q zelené led-diody LEDG. Pozn. Pro obě instance jsme použili delší názvy, což nám umožní lepší orientaci při simulaci obvodu Obvod je hotový, můžeme ho přeložit a vyzkoušet. Zde lze postupovat dvěma způsoby. Buď 1) přeložíme " prioritni2linearni.vhd", vytvoříme pro něj *.vwf soubor a provedeme simulaci v prostředí Quartus, viz stránky SPS kurzu. 2) nebo si napíšeme testbench a zkusíme obvod simulovat, což je preferovaný způsob používaný v profesionální praxi; teprve při něm vyniknou výhody VHDL oproti schématu. Popis postupu 2) je poněkud rozsáhlý a kvůli tomu byl zařazený do přílohy.
34
Závěr Příkladný úvod do VHDL není úplný. Nevznikl jako kompletní učebnice, ale jako rychlé seznámení se základy VHDL v prostředí Quartus. Chybí zde například části o reprezentaci čísel a dále především styl "behavioral", v němž se obvod popisuje svojí funkčností, která se od něho žádá. Po zkušenostech s předchozí výukou můžeme konstatovat, že studentům obvykle nedělá větší potíže tvořit v "behavioral" stylu, protože VHDL kód se v něm podobá klasickému programu. Problémy jim činí jedině provést vhodný popis řešení, ze kterého lze obvod dobře syntetizovat. Někteří "experti" z minulých let vyčerpali téměř celou kapacitu FPGA obvodu i jednoduchými stopkami se sedmisegmentovým displejem. Neprobrané části vyžadují delší vysvětlení, a tak si je necháme na přednášky. Úvod může obsahovat nejasnosti ve výkladu, případně překlepy, nepřesnosti či jiné prohřešky, které unikly korekturám. Autor uvítá, pokud mu o nich napíšete, nebo pošlete své připomínky a náměty na rozšíření stávajících částí pro příští školní rok. ~o~
35
Příloha A: Testbench pro ModelSim Ukážeme si, jak lze vytvořit simulaci pro ModelSim. Jde o profesionální techniku, kterou doporučujeme ve VHDL používat. Není rozhodně podmínkou pro absolvování kurzu SPS. Jakmile však zvládnete jednu simulaci, další vytvoříte již rychleji — postupy jsou podobné. Naučíte-li se aspoň krapánek "simulovat", můžete získat špetku výhod, minimálně ždibec úspory svého času pro trošku jiné aktivity.
A-1 Vytvoření programu typu Testbench Pokud chceme nějaký program zkoušet, musíme pro něj napřed napsat testbench, jakýsi generátor příslušných testovacích signálů. Při simulacích v Quartus se automaticky vytvářel ze souboru *.vwf, do kterého se v jeho editoru "Simulation Vaweform Editor" nakreslily požadované průběhy vstupů. Z nich se pak vygeneroval testbench. Pro vyzkoušení našeho "Program 20 - Prioritní dekodér s výstupem na lineární ukazatel" ze strany 34, by vstupní soubor "prioritni2linearni.vwf" mohl vypadat třeba takto:
Kreslíme se změnami po 20 ns postupně všechny možné vstupní kombinace pro klávesy KEY (tlačítko KEY dává ´0´ při stisknutí, a proto máme průběhy negované). Poté můžeme spustit Quartus II Simulator .Napřed nastavíme "Simulation->Options - Quartus II Simulator" a poté spustíme funkční simulaci, která testuje jen rovnice, a to příkazem "Simulation->Run Functional Simulation":
Funkční simulaci jsme sice provedli relativně rychle, ale tady končí naše časová úspora. Nyní začínáme pomalu luštit průběhy, zdalipak se náš obvod chová mravně a netropí nějaké neplechy. Louskání průběhů simulací si případně zopakujeme po každé změně v kódu. Po čase si položíme otázku, jestli by testování nešlo zautomatizovat. Podobnou možnost nabízí právě VHDL testbench. Pozn. U úloh zadávaných v kurzu SPS vystačíte s funkčními simulacemi doplněnými zprávami z Quartus překladače v položce" TimeQuest Timing Analyzer". Nebudete řešit tak náročné obvody, abyste museli zkoumat časové simulace uvažující i charakteristiky interních prvků FPGA. Ty sice ukáže i Quartus simulátor "Simulation->Run Timing Simulation", ale jen v méně přesné formě. 36
VHDL testbench uvedeme v podobě vhodné pro kombinační obvody a poté ho postupně vysvětlíme. Kód není těžký a lze ho velmi lehce modifikovat pro další obvody: --Testbench - Prioritni dekoder s linearnim ukazatelem library ieee; use ieee.std_logic_1164.all; entity tb_prioritni2linearni is end; architecture testbench of tb_prioritni2linearni is component prioritni2linearni is port ( KEY : in std_logic_vector(3 downto 1); LEDG : out std_logic_vector(2 downto 0) ); end component; type stimuls is array (0 to 7) of std_logic_vector(2 downto 0); constant sKEY : stimuls := ("000","001","010","011","100","101","110","111"); constant sLEDG : stimuls := ("000","001","011","011","111","111","111","111"); signal tbKEY : std_logic_vector(3 downto 1); signal tbLEDG : std_logic_vector(2 downto 0);
begin inst_prio2lin : prioritni2linearni -- instance testovaného programu port map (KEY=> tbKEY,LEDG=>tbLEDG); generator : process begin for i in stimuls'range loop tbKEY <=not sKEY (i); -- KEY davaji negovanou hodnotu wait for 20 ns; -- cekej 20 ns assert tbLEDG = sLEDG (i) report "vystupy odlisne pri indexu " & integer'image(i); end loop; wait; -- nekonecne cekani, simulace se zde zastavi end process; end architecture testbench;
Program 21 - VHDL testbech pro dekoder dekodér s výstupem na lineární ukazatel
Řádek 2 obsahuje standardní knihovny. Řádek 3 a 4 definuje entitu - pro programy typu testbech má vždy tento tvar, tj. uvádí se pouze její název tb_prioritni2linearni a chybí deklarace port nebo generic. Řádek 5 zahajuje architekturu. Nazvali jsme ji testbench. Řádek 6 až 9 vkládá deklaraci component zkoušené entity prioritni2linearni. Snadno ji vytvoříme známým postupem, a to okopírováním bloku entity ze souboru prioritni2linearni.vhd. Řádek 11 — vektory vstupů i výstupů prioritni2linearni mají stejné délky 3 členy, a tak si pro ně vytvoříme datový typ stimuls popisující pole osmi prvků typu std_logic_vector(2 downto 0); Řádek 12 obsahuje konstantní pole definující požadované simulační vstupy sKEY . Ty jsou přímou analogií vstupních signálů zadaných ve "Vaweform" editoru na předchozí stránce, akorát jsme je pro přehlednost zde nenapsali negované — tohle uděláme raději programově. Řádek 13 obsahuje definice požadovaných simulačních výstupů sLEDG, které budeme v programu automaticky kontrolovat. Řádek 14 a 15 obsahuje definice proměnných shodných se vstupy a výstupy testované komponenty, pro přehlednost jsme před jejich názvy napsali prefix tb od testbench. Řádek 18 a 19 obsahuje příkaz vytvoření instance zkoušeného obvodu prioritni2linearni, instanci pojmenujeme inst_prio2lin, a její vstupy a výstupy se připojíme na naše testovací signály. Řádek 20 deklaruje blok procesu. Příkaz patří do zde neprobraného stylu "behavioral" programování. Uvnitř procesů se používají příkazy zařazené do skupiny "Sequential Statements" — ty se neprovádějí 37
paralelně, ale vykonávají se sekvenčně jeden po druhém tak jako v běžných programech. Musíme však zdůraznit, že způsob, jakým je zde napsaný proces, se povoluje jedině v simulacích, ale v těch má zase většinou tento tvar. Pro syntézu obvodů se proces musí psát jiným způsobem, o němž si budeme povídat na přednáškách - tenhle proces by v reálném obvodu nefungoval. Strukturu procesu a jeho součinnost s testovaným obvodem ilustruje následující obrázek. Z něho vidíme, že z procesu vznikl obvod generátoru připojený k testované entitě.: tb_prioritni2linearni generator : process priority2linearni
Řádek 22 zahajuje programový sekvenční cyklus for - loop , který do proměnné cyklu i typu integer přiřazuje postupně hodnoty 0 až 7, tj. všechny indexy pole simulačních vstupů. Řádek 23 načte testovací vstup z indexu i a jeho negovanou hodnotu pošle po signálu tbKEY příkazem "tbKEY <=not sKEY (i); " vedoucím na vstup KEY ( port map na řádku 19). Řádek 24 obsahuje pokyn pro simulátor, aby se další příkaz bloku procesu vykonal až za 20 nanosekund. Zdržení jsme vložili kvůli tomu, aby nové hodnoty na vstupech KEY měly čas k ovlivnění výstupů LEDR. Pauza se týká jen procesu, simulátor bude dál pilně počít reakce inst_prio2lin na vstupy. Řádek 25 až 26 testuje, samozřejmě až po uplynutí předchozího čekání 20 ns, zda výstupy obvodu se shodují s požadovanými. Při neshodě se vypíše chybové hlášení obsahující zadaný text a hodnotu indexu převedenou na řetězec pomocí atributu integer'image(i); Pozn. Hodnoty vstupů a výstupů nevypisujeme záměrně. Konverze typu std_logic_vector na řetězec je sice možná, ale potřebné funkce nejsou zahrnuté ve standardních knihovnách a musely by se vkládat celé jejich definice. Pro zjednodušení kódu jsme je raději vynechali. Zájemci naleznou vše potřebné na webu. Řádek 27 končí cyklus a musí mít tvar "end loop;" — zde za end zde nelze vynechat loop. Řádek 28 obsahuje příkaz wait, avšak bez zadaného času. Simulátor bude čekat nekonečně dlouho, tj. zastaví se simulace. Řádek 29 ukončuje blok procesu a opět zde nelze za end vynechat klíčové slovo process. Řádek 30 končí architekturu. Máme již trochu delší kód, a tak jsme místo jednoduchého koncového end zvolili i přidání klíčového slova architecture a názvu architektury testbench.
Budeme-li chtít náš testbench použít pro zkoušení jiného obvodu, vyměníme jen prioritni2linearni komponentu za novou a upravíme kód pro její instanci. Jakmile definujeme požadované testovací vektory, můžeme zkoušet. Testbench stačí jen spustit a ihned víme, zda pracuje dle našich požadavků. Pozn. V Quartusu II není tb_prioritni2linearni.vhd přeložitelný kvůli použití wait for