- Úvod do PLD a jazyka VHDL
- Základní konstrukce ve VHDL
- CPLD a první aplikace
Nedávno jsem se tady zabýval programovatelnými obvody CPLD. K implementaci jednoduchého příkladu jsem zvolil jazyk VHDL. V tomto článku bych se chtěl tímto jazykem zabývat podrobněji. Ukážu některé základní a nejpoužívanější konstrukce a pokusím se vysvětlit základní pojmy používané při programování PLD.
Základem jsou bloky
Je důležité si uvědomit, že zdrojovým kódem napsaným ve VHDL se snažíme popsat chování hardware. Nejedná se o žádný kód, který bude zpracovávat procesor, ale o kód, který popisuje chování digitálního obvodu. Na ten můžeme nahlížet jako na různě pospojované bloky. Každý blok má své vstupy a výstupy a vykonává nějakou předem danou činnost.
Každý projekt, který budeme ve VHDL psát, si tedy rozdělíme na základní bloky. Dejme tomu, že chceme napsat SPI slave
. Základem bude blok SPI, který bude mít vstupy SCK
, SS
a MOSI
a jeden výstup MISO
. To máme hlavní blok. Ten se dá rozdělit na dva podbloky – posuvný registr a čítač. Ty napíšeme ve VHDL a spojíme je tak, aby fungovaly jako SPI slave.
Tímto způsobem lze rozdělit libovolně složitý projekt na množství jednoduchých bloků, které lze ve VHDL implementovat na několika málo řádcích. Jeden blok ve VHDL vypadá následovně:
entity NÁZEV_BLOKU is Port ( -- -- Definice vstupů a výstupů -- ); end NÁZEV_BLOKU; architecture Behavioral of NÁZEV_BLOKU is -- -- Definice proměnných a signálů -- begin -- -- Vlastní kód -- end Behavioral;
To, jak blok vypadá zvenku, definuje část v entity
. Zde jsou všechny vstupy a výstupy, které lze použít. Samotný kód, který definuje chování bloku je v sekci architecture
, která je ještě rozdělena na definici proměnných a signálů a na vlastní kód. Tímto definujeme jak se má blok chovat. To, jak bude doopravdy vypadat hardware pak záleží na překladači, který náš kód interpretuje.
Základní datové typy
Mezi základní datové typy, které se používají téměř pořád patří std_logic
a std_logic_vector
. Prvně jmenovaný typ popisuje jeden bit, zatímco druhý popisuje více bitů. Slovo bit berte s rezervou, použil jsem jej pouze pro představu. Doopravy se jedná o signálovou cestu. Aritmetika nad těmito typy je definována v knihovnách IEEE.STD_LOGIC_ARITH.ALL
a IEEE.STD_LOGIC_UNSIGNED.ALL
nebo IEEE.STD_LOGIC_SIGNED.ALL
.
Uveďme si pár příkladů:
signal A: std_logic := '1'; signal B: std_logic := 'Z'; -- signal C: std_logic_vector(7 downto 0) := X"FE"; signal D: std_logic_vector(3 downto 0) := "01ZZ"; signal E: std_logic_vector(31 downto 0) := (others => '0');
Signály A
a B
jsou typu std_logic
. Zároveň v definici signálu jsme provedli i deklaraci (přiřadili jsme signálům jejich počáteční hodnoty hned během jejich vytvoření). Signál A
nabývá hodnoty log. jedničky a signál B
je ve stavu vysoké impedance.
Signály C
, D
a E
jsou typu std_logic_vector
. Signál C
je 8 bitový a jeho počáteční hodnota je nastavena na 0xFE
. Signál D
je 4 bitový a nejvyšší dva bity jsou nastaveny na 01
a nižší dva bity jsou ve stavu vysoké impedance. Poslední signál, E
, je 32 bitů široký a všechny jsou nastaveny na log. nulu.
Tyto definice patří do sekce architecture
, před klíčové slovo begin
. V samotném kódu se s nimi pracuje následovně:
A <= '0'; -- Přiřazení log. nuly signálu A. D <= X"F"; if B = '1' then -- Spojení dvou signálů. C <= A & C(7 downto 1); else C <= C end if; E <= (others => 'U'); -- 'U' je nedefinovaný stav.
U signálů A
a D
provedeme obyčejné přiřazení. U signálu C
je spojení dvou signálů pomocí operátoru &
. To znamená, že k horním 7 bitům signálu C
připojíme signál A
. V tomto našem případě jsme provedli bitový posun o jeden bit doleva/doprava. Signálu E
jsme přiřadili všechny bity na U
, což je nedefinovaný stav (většinou nežádoucí).
Klíčové slovo process
Již v minulém článku jsem upozorňoval na fakt, že všechny řádky se vykonávají okamžitě, bez ohledu na pořadí. To znamená, že tento kus kódu
C <= A + B; Z <= C;
je funkčně stejný jako
Z <= C; C <= A + B;
Pokud potřebujeme, aby se určitá část kódu změnila jen někdy za určité podmínky, můžeme použít klíčové slovo process
:
process (A) begin if A = '1' then B <= '1'; end if; end process;
Pokud signál A
jakkoliv změní svůj stav, vykoná se i obsah celého bloku process
. V našem případě se provede přiřazení log. jedničky do signálu B
v případě, že signál A
nabývá hodnoty log. jedničky.
Často se process
používá k implementaci sekvenční logiky, která je řízena hodinovým signálem:
signal (clk) begin if rising_edge(clk) then counter <= counter + '1'; if counter = X"FE" then overflow <= '1'; else overflow <= '0'; end if; end if; end signal;
Tímto jsme vytvořili jednoduchý čítač, jehož hodnota se inkrementuje s každou náběžnou hranou hodinového signálu a při přetečení se krátce nastaví signál overflow
.
Základní konstrukce
V této části ukážu některé základní nejpoužívanější bloky. Každý kus kódu stručně okomentuji. Nicméně vše by mělo být jasné, půjde o jednoduché příklady a budou obsahovat jen to, co jsem již vysvětlil.
Multiplexor
Multiplexor bude obsahovat 4 vstupy (A
, B
, C
, D
), které budou na výstup (Y
) vybírány pomocí dvou bitů (sel
).
library ieee; use ieee.std_logic_1164.all; entity Mux is port ( sel: in std_logic_vector(1 downto 0); A, B, C, D: in std_logic; Y: out std_logic ); end Mux; architecture behavior of Mux is -- Žádné signály nejsou potřeba. begin process (sel, A, B, C, D) begin case sel is when "00" => Y <= A; when "01" => Y <= B; when "10" => Y <= C; when "11" => Y <= D; when others => Y <= A; end case; end process; end behavior;
Proces se spustí při každé změně některého z 5 signálů (sel
, A
, B
, C
a D
). Pro rozhodnutí, který vstup půjde na výstup Y
, jsem použil konstrukci case - when
. Jedná se o známé switch - case
z jiných programovacích jazyků.
Co je zde velmi důležité, je poslední přepínač when others
. Ten ošetřuje jakýkoliv jiný stav, který by mohl na vstupu sel
nastat. Pokud bychom jej nenapsali, obvod by se buď choval v některých případech nedefinovaně nebo by překladač interpretoval kód mnohem méně efektivně.
Kód je možné podle potřeby rozšířit na multiplexor 8:1 i více.
Čítač
Osmi bitový čítač, jehož aktuální hodnotu lze číst na výstupu. Při přetečení se na výstupu objeví logická jedna po dobu jedné periody hodinového signálu. Čítač jde vynulovat asynchronním signálem reset.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity Counter is port ( rst : in std_logic; clk : in std_logic; count: out std_logic_vector(7 downto 0), overflow: out std_logic, ); end Counter; architecture behavioral of Counter is signal temp: std_logic_vector(count'range) := (others => '0'); begin process (clk, rst) begin if rst = '1' then temp <= (others => '0'); elsif rising_edge(clk) then temp <= temp + '1'; if temp = X"FE" then overflow <= '1'; else overflow <= '0'; end if; end if; end process; count <= temp; end behavioral;
Zde je jedna věc, o které jsem se ještě nezmiňoval – count'range
. Tato konstrukce vytvoří signál temp
o stejné šířce jako je count
. Klíčové slovo range
je tak nahrazeno za 7 downto 0
. Další klíčová slova, která lze využít jsou 'high
(pozice MSB), 'low
(pozice LSB), 'length
(celková delka signálu) a 'reverse_range
(vrátí to samé jako 'range
, ale s opačným pořadím).
Dalším důležitým poznatkem, kterého byste si měli v tomto kódu všimnout, je použití pomocné proměnné temp
. Tato proměnná je vlastně identická s výstupem count
. Tak proč jsem použil nový signál a ne přimo výstup count
? Důvod je prostý, výstupní signály nelze za žádných okolností číst. Slouží pouze jako výstup. Pokud nastane situace, kdy potřebujeme číst stav výstupního signálu, pak je nutné použít pomocnou proměnnou/signál (to platí i pro výstup použitý v podmínce if
– nelze).
Dekodér
Dekodér binárního kódu na 1z8. Obecně se ale může jednat o libovolný dekodér, např. na 7 segmentový displej.
library IEEE; use IEEE.STD_LOGIC_1164.ALL, IEEE.NUMERIC_STD.all; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity Decoder is port ( A : in std_logic_vector (2 downto 0); Y : out std_logic_vector(7 downto 0) ); end Decoder; architecture behavioral of Decoder is begin process (A) begin case A is when "000" => Y <= "00000001"; when "001" => Y <= "00000010"; when "010" => Y <= "00000100"; when "011" => Y <= "00001000"; when "100" => Y <= "00010000"; when "101" => Y <= "00100000"; when "110" => Y <= "01000000"; when "111" => Y <= "10000000"; when others => Y <= "00000000"; end case; end process; end behavioral;
Posuvný registr
Posuvný registr, kterému lze přepnutím vstupu dir
změnit směr posunu.
library IEEE; use IEEE.STD_LOGIC_1164.ALL, IEEE.NUMERIC_STD.all; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity Shift is port ( clk: in std_logic; dir: in std_logic; input: in std_logic; Y : out std_logic_vector(7 downto 0) ); end Shift; architecture behavioral of Shift is signal temp: std_logic_vector(Y'range) := (others => '0'); begin process (clk) begin if rising_edge(clk) then if dir = '1' then temp <= input & temp(temp'high downto temp'low-1); else temp <= temp(temp'high-1 downto temp'low) & input; end if; end if; end process; Y = temp; end behavioral;
Tento kód by se dal ještě vylepšit jedním výstupním signálem data_ready
, který by signalizoval, že se registr již celý přetočil a nový bajt je nachystaný na výstupu, ale to už byste měli zvládnout sami (chce to jeden signál, který bude čítat počet hodinových cyklů).
Závěr
V článku jsme si ukázali některé základní postupy a konstrukce jazyka VHDL, které se mohou hodit při práci s PLD obvody. Je důlěžité si uvědomit, že vaším kódem popisujete chování reálného hardware. Vy sami ale nemáte možnost jak výsledný hardware ovlivnit, to za vás dělá překladač. Ten může váš kód pochopit dobře a výsledek optimalizovat, ale také nemusí a výsledná struktura bude velmi neefektivní. Hodně záleží na každém řádku i procesu. Více než kde jinde zde platí, že je potřeba dvakrát přemýšlet, než něco napíšete.
Pokud máte k VHDL jakoukoliv otázku, můžete se ptát na fóru qa.uart.cz. Jsem si vědom, že jsem vůbec neprobral jak použít VHDL k simulacím, jak na cykly (for
a while
, ty jde použit pouze při simulacích, do reálného hardware jsou syntetizovat pouze těžko) a mnoho dalšího. Tímto odkazuji na některou z mnoha učebnic, případně na některý budoucí článek.