Bit sem, bit tam…

I počítače jsou alespoň osmibitové. Buďme i my vícebitoví!

Až dosud jsme si ukazovali všechno jednobitové: Jednobitová sčítačka s jednobitovými daty, jednobitové signály… Copak VHDL neumí udělat pořádnou sběrnici, třeba datovou, osmibitovou? No, umí. A dokonce hned několika způsoby.

Vektor

Signál, který je vícebitový, tj. obsahuje několik signálů typu std_logic, lze ve VHDL zapsat jako vektor. Příklad – osmibitová datová sběrnice bude:

„std_logic“ se změnilo na „std_logic_vector“, a za tímto typem je zapsaný rozsah 7 až 0 (downto počítá směrem dolů, to směrem nahoru). Tedy DBUS je signál, skládající se z osmi vodičů s typem std_logic, očíslovaných 7, 6, 5, … 0. Proč takhle, proč ne 0 .. 7? Zápis je od nejvýznamnějšího bitu k tomu nejméně významnému (od MSB k LSB) a v tomto případě to je tak, že nejvýznamnější je D7. Hodí se to, když někde chcete pracovat s hodnotou tohoto signálu ne v podobě bitového zápisu, ale v podobě čísla.

Jak přiřadíme hodnotu?

Všimněte si důležité věci: Vícebitové hodnoty (vektory) se zapisují v uvozovkách (na rozdíl od jednobitových hodnot v apostrofech). Podobně je tomu i v C, kde se znak dává do apostrofů, řetězec do uvozovek.

První řádek představuje prosté přiřazení všech bitů, druhý taky, ale se zjednodušeným zápisem v hexadecimální podobě. Třetí řádek používá výčet – v závorce, oddělené čárkami, jsou zapsány dvojice „bit=>hodnota“. Speciální klíč „others“ znamená „všechny ostatní bity, zde nevyjmenované“. Na čtvrtém řádku je ukázáno, jak se tato vlastnost využívá často pro nastavení všech bitů na určitou hodnotu. Pátý řádek modifikuje výčet, syntax „1 to 3“ označuje bity 1 až 3. Na šestém řádku jsou bity zapsány tak jak jdou po sobě. Sedmý řádek pak slouží jako připomenutí toho, že hodnota nemusí být jen 0 nebo 1, ale třeba i „vysoká impedance“, tedy Z.

Jednotlivé bity se odkazují pomocí zápisu s indexem v kulaté závorce (pozor na zvyk z C a spol., kde se píší do hranatých), takže například DBUS(0).

Čtyřbitová sčítačka

Pokračujme v našem příkladu a sestavme si ze čtyř jednobitových sčítaček jednu čtyřbitovou. Její zapojení je očividné: Dvě vstupní hodnoty A a B budou tentokrát čtyřbitové vektory, totéž výstup Q. Cin je připojen na vstup Cin sčítačky nejnižšího řádu, Cout na výstup Cout sčítačky nejvyššího řádu, a zbytek je propojen tak, že přenos z nejnižšího řádu vede do řádu vyššího… atd. Nějak takhle:

Deklarace entity je jasná, o té není potřeba diskutovat. Architektura této sčítačky obsahuje komponentu fulladder, definovanou minule, a čtyři interní signály C0 až C3, pomocí kterých budeme propojovat výstup Cout jedné sčítačky se vstupem Cin druhé. V těle jsou pak vytvořeny čtyři instance jednobitové sčítačky, porty jsou namapovány tak, jak jsme si popsali (A na bity sběrnice A, B na bity sběrnice B, Q na jednotlivé bity z Q, do Cin jsou zapojeny výstupy Cout předchozích stupňů, nejvyšší Cout vede ven a nejnižší Cin je připojen na vstup Cin. Takto sčítačka funguje bez problémů. Pojďme si ji ale trochu zesložitit.

Funkčně je zcela ekvivalentní, jen nejsou definované čtyři signály, ale pětibitový vektor C. Programátor možná v tuhle chvíli zajásá, protože objeví v zápise jednotlivých instancí určitou logickou strukturu. A pokud se těšíte, že budete moci sčítačky nějak vygenerovat pomocí cyklu, tak jste na správné stopě:

Definujeme si víc sčítaček pomocí konstrukce for {proměnná} in {rozsah} generate … end generate; a uvnitř pracujeme s N jako s normální proměnnou, pomocí které indexujeme jednotlivé bity ve vektoru. Pro rozsah 0 to 3 vzniknou čtyři sčítačky, jejichž porty budou nastaveny naprosto stejně jako v předchozím příkladu.

Generické entity

Pokud jste programátor, přijde vám to přirozené: Proč definovat sčítačku čtyřbitovou, osmibitovou, a pro každou použitou šířku vlastní, když by stačilo definovat obecnou sčítačku Nbitovou, a pak by se při vytváření instancí řeklo, že tahle bude čtyřbitová a tahle šestnáctibitová. Šlo by to?

Šlo, děkujem za optání. Vezmeme předchozí definici, tu s generátorem instancí, a řekneme, že šířku si uložíme do parametru „wide“. Když bude 4, půjde o čtyřbitovou sčítačku. Všude, kde se vyskytuje trojka, tak ji nahradíme „wide-1“ (třeba ve výrazech „3 downto 0“), kde se vyskytuje čtyřka, tam ji nahradíme „wide“.

Samozřejmě bude potřeba někde ten parametr „wide“ nadeklarovat. Pro deklarace je určená entita, a přesně tam přijde deklarace parametru, a to do části generic(). Takto:

Parametry jsou zase zapsané v části generic() podobně jako vstupně-výstupní signály v části port, jako {jméno}:{typ}[:={default hodnota}]. Zde je použitý typ integer, tedy celé číslo, bez defaultní hodnoty, tj,. šířku musíme vždy zadat.

Jak se taková komponenta používá? Velmi podobně jako negenerická. V architektuře musíte uvést deklaraci komponenty, která je shodná s deklarací entity (včetně té části generic), a u instance zapíšeme kromě port map ještě generic map. Stejným způsobem, jakým uvádíme signály pro port, uvedeme i generické parametry. Příklad použití v testovacím zapojení:

Generic map nastaví parametr „wide“ na hodnotu 4, port map pak přiřadí porty.

Clipboard01

Alternativní zápis map

Pokud se vám nelíbí pravidlo „dodržet pořadí“, můžete využít zápisu s pojmenovanými parametry, třeba:

Připomínám, že VHDL ignoruje konce řádků a mezery, takže používejte s klidným svědomím zápis takový, jaký se vám líbí.

Aritmetika (s velkým vykřičníkem!)

Když už jednou ty vektory jsou, tak by bylo fajn mít možnost s nimi pracovat jako s čísly, že? Ukážu vám, jak to jde udělat, a zároveň důrazně varuju, abyste to tak nedělali, a důvod vám prozradím o kousek níž.

Představte si, že abstrahujeme od toho, že signál jsou nějaké bity vedle sebe, a místo toho s nimi pracujeme jako s číselnými hodnotami. Takže osmibitový signál je buď „0 .. 255“, nebo „-128 .. +127“, to podle toho, jestli si ho definujeme jako signed, nebo unsigned. Použijeme další dvě knihovny:

Co tím získáte? Tak například možnost pracovat s čísly typu signed a unsigned s danou šířkou v bitech, a k nim máte definované základní matematické operace. Takto bychom čtyřbitovou sčítačku nadefinovali tak, že nepoužívá vektory, ale „unsigned (3 downto 0)“, a samotné sčítání by bylo „Q <= A + B + Cin;“ – takhle prosté, protože máme „plus“ definované. Syntetizér si s tím už nějak poradí, a pokud má daný obvod například integrované hardwarové sčítačky, tak použije je. Ve skutečnosti to je ovšem o něco větší peklo, protože často musíme přetypovávat z unsigned / signed (s omezeným rozsahem) na typ integer (obecné celé číslo), kód máme zaflákaný nejrůznějšími conv_integer(x) a conv_unsigned(x,4) (to jako že na šířku 4 bity), a když to chcete simulovat, tak zjistíte, že to, co syntetizér nějak přeloží, to vám simulátor vyhodí, že tomu nerozumí. Například ve výrazu (A+B)>15 (pro zjištění přenosu) tvrdí, že neví, jaký operátor „>“ použít, a tak si vytváříte další signály… Je to možná pěkná vymoženost, ale někdy to opravdu bolí. Každopádně když to budete chtít použít, nepoužívejte std_logic_arith! Proč?

Arith, SLV, nebo Numeric?

Co by to bylo za jazyk, kdyby neměl nějakou pasáž, která rozděluje jeho příznivce na dva nesmiřitelné tábory (a dva menší tábory heretiků). Ve VHDL jsme si už ukázali svatou otázku „std_logic vs std_ulogic“, ale máme ještě jednu, možná mohutnější, totiž „logic_arith & std_logic_vector vs numeric“. Má to celé historické pozadí, jak někdo navrhnul jeden standard a ostatní byli nespokojení, ne snad proto, že by standard nebyl dobrý, ale protože ho nenavrhl orgán, který standardně standardy standardizuje (IEEE), a tak navrhli jiný standard, který umí defakto úplně totéž, ale není kompatibilní, ovšem aby to nebylo tak jednoduché, tak to celé má dobrý důvod a je mezi tím rozdíl, a pokud znáte HTML, tak vám řeknu, že je mezi tím rozdíl jako mezi <em> a <i>.

Totiž, ten druhý standard, standardní od IEEE, není kompatibilní se std_logic_arith, protože zavádí stejně pojmenované typy. Pokusíte-li se použít obojí najednou, bude zle. Pokud chcete použít čísla, použijte knihovnu numeric_std, tedy na začátku uveďte:

Proč tedy všichni nepoužívají numeric_std? Nechci se tu pouštět do výčtu argumentů, ale na jedné straně jsou ti, kteří hlásají: „Numeric! Je to standard IEEE, takže tím to je dané!“ Na druhé straně jsou ti, kteří říkají: „Ale většina knihoven používá vector, protože tak elektronické obvody fungují! Když použiju numeric, musím přetypovávat… Takže logicky vector a arith“

Mně je nejsympatičtější třetí tábor, který tvrdí, že obojí má svoje místo. Tam, kde se na vícebitový signál pohlíží jako na číslo (třeba právě u sčítačky), tam patří numeric_std (ne arith). Tam, kde vícebitový signál nemá rozměr čísla, tam použít std_logic_vector (někdy se též setkáte s označením SLV). Například osmibitový vstup multiplexeru, nebo sběrnice pro přerušení IRQ0 – IRQ7 jsou typické případy, kdy neříkáme „přišlo přerušení 8“, ale „přišlo přerušení IRQ3“ – tedy SLV.

Pokud je potřeba převést, tak se nevyhnete přetypování. Ale vyhněte se std_logic_arith, std_logic_unsigned a std_logic_signed. Tyto knihovny jsou zavržené (deprecated), navíc jsou méně flexibilní než numeric_std. (K tématu viz např. zde a zde)

Příště: Matematické operátory a atributy.