Čeština / English
Login

Návody / Popis HW pomocí VHDL

Autor: Zdeněk Vašíček ()
Update: 20.5.2009

Při psaní VHDL kódu se můžeme setkat s nepříjemným problémem - v simulaci funguje vše jak má, avšak po syntéze v hardware obvod nefunguje. Problémem může být právě onen proces syntézy, který nemusí pochopit námi vytvořené konstrukce tak, jak jsme je zamýšleli a může vytvořit obvod, který pracuje mírně odlišně. Pokud však budeme využívat konstrukcí uvedených níže (tzv. syntetizovatelných šablon), máme jistotu, že nástroj provádějící syntézu tyto konstrukce rozpozná a správně interpretuje. Výsledký obvod pak bude pracovat tak, jak jsme zamýšleli. V praxi se ukazuje, že v drtivé většině případů je možné si vystačit pouze se šablonami uvedenými níže, případně jejich modifikacemi.

1. Šablony pro kombinační obvody

Logické obvody lze rozdělit do dvou skupin - kombinační obvody a sekvenční obvody. Kombinační obvody jsou z hlediska konstrukce jednodušší, neboť neobsahují žádný paměťový prvek. VHDL kódy pro několik základních kombinačních obvodů můžete nalézt v této kapitole. Od sekvenčních obvodů je odlišuje absence řídícího hodinového signálu, který určuje okamžik, kdy dochází ke změně stavu.

1.1. Multiplexor 4 na 1 (MUX 4to1)

stem_mux4.png

Zápis multiplexoru pomocí příkazu podmíněného přiřazení (conditional assigment)

Y <= A when (SEL = "00") else
     B when (SEL = "01") else
     C when (SEL = "10") else
     D;

Zápis multiplexoru pomocí konstrukce with-select

with SEL select
   Y <= A when "00",
        B when "01",
        C when "10",
        D when others;

Zápis pomocí konstrukce case uvnitř procesu

process(A,B,C,D,SEL)
begin
   case SEL is
      when "00" => Y <= A;
      when "01" => Y <= B;
      when "10" => Y <= C;
      when others => Y <= D;
   end case;
end process;

Zápis pomocí konstrukce if - elsif uvnitř procesu

process(A,B,C,D,SEL)
begin
   if (SEL="00") then
      Y <= A;
   elsif (SEL="01") then
      Y <= B;
   elsif (SEL="10") then
      Y <= C;
   else
      Y <= D;
   end if;
end process;

Pokud použijete zápis pomocí procesu, ujistěte se, že na sensitivity listu jsou všechny signály, na jejichž změnu musí obvod ihned reagovat. Dále se zapotřebí správně ošetřit všechny stavy příkazu case, jinak se může stát, že bude vysyntetizován paměťový prvek (varovná hláška inferring latch). Abychom této chybě předešli, je možné použít konstrukci využívající výchozí hodnoty, jak ukazuje následující kód. Tento způsob zápisu je vhodné využívat při zápisu automatu.

Zápis pomocí konstrukce if - elsif uvnitř procesu, ošetření výchozího stavu méně náchylné na zavedení chyby

process(A,B,C,D,SEL)
begin
   Y <= D;
   if (SEL="00") then
      Y <= A;
   elsif (SEL="01") then
      Y <= B;
   elsif (SEL="10") then
      Y <= C;
   end if;
end process;

1.2. Enkodér

Zápis enkodéru je velmi podobný zápisu multiplexoru a je možné proto použít všechny výše uvedené konstrukce. V následující ukázce si uvedeme pouze variantu využívající příkazu with-select, který je z hlediska množství napsaného kódu nejefektivnější.

stem_enc4to2.png

Uvedený enkodér (4 to 2 encoder) provádí převod z kódu "1 z 4" na dvoubitový binární kód. To, jak se postavíme k ošetření chybné vstupní kombinace záleží pouze na nás. V následující ukázce se pouze přiřadí hodnota "XX", která nás v simulaci upozorní na chybu. Pokud může v reálném obvodu nastat chybná vstupní kombinace, pak je většinou zapotřebí ji detekovat a ošetřit.

with A select
   Y <= "00" when "0001",
        "01" when "0010",
        "10" when "0100",
        "11" when "1000",
        "XX" when others;

Jelikož tento obvod vykazuje určitou pravidelnost, nabízí se možnost jej zapsat pomocí konstrukce for-loop. Smyčku je vhodné využívat v případě, kdy by zápis pomocí jiných výčtových konstrukcí obsahoval mnoho možností. Tehdy se totiž vyplatí popsat chování algoritmicky. Možností zápisu je několik, uvedeme jednu z nejběžnějších.

process(A)
variable n: integer range 0 to 3;
begin
   Y <= "XX";
   for n in 0 to 3 loop
      if (A=conv_std_logic_vector(n, 4)) then
         Y <= conv_std_logic_vector(n, 2);
      end if;
   end loop;
end process;

1.3. Dekodér

Dekodér má opačnou funkci než encoder. Úlohou je dekódovat vstupní vektor tvořený několika málo signály na výstup, který zpravidla tvoří mnohem více signálů.

stem_decaddr.png

Nejpoužívanějším dekodérem je adresový dekodér (address decorer, enable decoder), který na základě vstupní adresy generuje povolovací signály např. pro jednotlivé registry, paměťové banky, periferie apod. Zápis je opět velmi podobný výše uvedeným konstrukcím, uvedeme proto pouze zápis pomocí case. Opět by bylo možné s výhodou použít konstrukci for-loop.

process(ADDR)
begin
   EN <= "0000"
   case ADDR is
      when "00" => EN(0) <= '1';
      when "01" => EN(1) <= '1';
      when "10" => EN(2) <= '1';
      when "11" => EN(3) <= '1';
      when others => null;
   end case;
end process;

Výše uvedený kód je možné také zapsat pomocí řady nezávislých komparátorů.

EN(0) <= '1' when ADDR="00" else '0';
EN(1) <= '1' when ADDR="01" else '0';
EN(2) <= '1' when ADDR="10" else '0';
EN(3) <= '1' when ADDR="11" else '0';

2. Šablony pro sekvenční obvody

2.1. Latch

Sekvenční obvody je možné rozdělit na dvě skupiny - tzv. latche (hladinově sensitivní) a flip-flop registry (hranově sensitivní klopné obvody). Rozdíl spočívá v tom, kdy dochází ke vzorkování vstupu. Latch registry vzorkují vstup po celou dobu, kdy je řídící hodinový signál v logické 1. Způsob zápisu latch registru vypadá následovně

process(EN, A)
begin
   if (EN='1') then
      Y <= A;
   end if;
end process;

2.2. Registr

Mnohem používanější jsou však synchronní hranově sensitivní klopné obvody (KO), které vzorkují vstupní signál pouze při vzestupné případně sestupné hraně řídícího hodinového signálu CLK. Nejjednodušším obvodem je KO typu D (delay). Tento obvod má schopnost si pamatovat stav, který byl na vstupu při vzestupné hraně hodinového signálu.

stem_dregsr.png

VHDL kód pro klopný obvod typu D s asynchronním nulováním je uveden na následující ukázce. Sice existuje několik dalších variant zápisu, avšak níže uvedená konstrukce je nejpřehlednější.

process(RST, CLK)
begin
   if (RST='1') then
      Q <= '0';
   elsif (CLK'event) and (CLK='1') then
      Q <= D;
   end if;
end process;

VHDL kód pro tentýž klopný obvod, avšak se synchronním nulováním je následující.

process(CLK)
begin
   if (CLK'event) and (CLK='1') then
     if (RST='1') then
        Q <= '0';
     else
        Q <= D;
     end if;
   end if;
end process;
stem_dregsre.png

Často je zapotřebí přidat ke klopnému obvodu povolovací (enable) vstup. Je možné přidat podmínku EN='1' k příkazu if, avšak to se nedoporučuje, neboť tato konstrukce nemusí být při syntéze správně rozpoznána. Korektní zápis je uveden na následující ukázce.

process(RST, CLK)
begin
   if (RST='1') then
      Q <= '0';
   elsif (CLK'event) and (CLK='1') then
      if (EN='1') then
         Q <= D;
      end if;
   end if;
end process;

2.3. Posuvný registr

N-bitový posuvný registr je sada n jednobitových klopných obvodů, které jsou zapojeny do kaskády a mohou po bitech bitově posouvat svůj obsah směrem k dalšímu případně předchozímu bitu. Posuvné registry je možné použít například pro převod sériového přenosu na paralelní a naopak. Pro efektivní zápis posuvného registru je možné použít operátor & jazyka VHDL, jak ukazuje následující ukázka osmibitového posuvného registru se seriovým vstupem DI, paralelním výstupem DATA a povolovacím vstupem EN.

stem_shreg.png

process(RST, CLK)
begin
   if (RST='1') then
      DATA <= (others => '0');
   elsif (CLK'event) and (CLK='1') then
      if (EN='1') then
         DATA <= DI & DATA(7 downto 1);
      end if;
   end if;
end process;

Pokud není zapotřebí v aplikaci nulovat obsah posuvného registru, je lepší se výše uvedené konstrukci vyhýbat, neboť posuvný registr bez nulování lze efektivně sestrojit s využitím funkčních generátorů. Jelikož funkční generátory neumí resetovat svůj obsah, musí být výše uvedená konstrukce vysyntetizována z kaskády běžných registrů, což může znamenat větší nároky na počet zdrojů FPGA.

process(CLK)
begin
   if (CLK'event) and (CLK='1') then
      if (EN='1') then
         DATA <= DI & DATA(7 downto 1);
      end if;
   end if;
end process;

2.4. Čítač

Pokud použijeme aritmetickou knihovnu obsahující základní operace nad bitovým vektorem, je možné zapsat čítač pomocí operátoru +. Čítač se synchronním nulováním a povolovacím vstupem EN je uveden na následující ukázce. Tento obvod čítá do maximální hodnoty dané počtem bitů ADDR (tento signál je typu std_logic_vector). Poté automaticky přeteče a začně čítat opět od hodnoty 0.

use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
 
architecture beh of citac is
  cnt : std_logic_vector(3 downto 0);
begin
 
  ADDR <= cnt;
 
  process(CLK)
  begin
     if (CLK'event) and (CLK='1') then
        if (RST='1') then
           cnt <= (others => '0');
        elsif (EN='1') then
           cnt <= cnt + 1;
        end if;
     end if;
  end process;
 
end architecture;

Pokud potřebujeme čítat pouze do určité hodnoty, je zapotřebí přidat komparátor, který detekuje poslední hodnotu adresy a způsobí v příštím hodinovém taktu znulování čítače. Následující ukázka zachycuje čtyřbitový čítač čítající cyklicky od hodnoty 0 do hodnoty 9.

ADDR <= cnt;
cnt_load <= '1' when (cnt="1001") else '0';
 
process(RST, CLK)
begin
   if (CLK'event) and (CLK='1') then
      if (RST='1') then
         cnt <= (others => '0');
      elsif (EN='1') then
         if (cnt_load='1') then
            cnt <= (others => '0');
         else
            cnt <= cnt + 1;
         end if;
      end if;
   end if;
end process;

3. Šablony pro konečné automaty

Automat se obvykle píše pomocí tří (případně dvou) procesů s následujícími funkcemi

  • proces present state - tento proces se stará o uchování stavu automatu. Typicky obsahuje registr s asynchronním nulováním, jehož výstupem je aktuální stav automatu (pstate)

  • proces next state logic - čistě kombinační část, která má za úkol generovat následující stav

  • proces output logic - opět kombinační logika, která se stará o generování výstupu atutomatu. Podle toho, zda jsou výstupy odvozeny pouze pomocí aktuálního stavu pstate rozlišujeme mezi Moorovým automatem a Mealyho automatem, kde je výstup je odvozen nejen od stavu ale i od dalších vstupních signálů.

Procesy starající se o generování následujícího stavu a výstupu se v praxi spojují do jednoho.

Ukázka jednoduchého dvoustavového Moorova automatu se stavy SInit a SRead. Ve stavu SRead se generuje na signálu Y logická 1 a automat v tomto stavu setrvá.

--Present State registr
pstatereg: process(RST, CLK)
begin
   if (RST='1') then
      pstate <= SInit;
   elsif (CLK'event) and (CLK='1') then
      pstate <= nstate;
   end if;
end process;
 
--Next State logic
nstate_logic: process(pstate, A)
begin
   -- default values
   nstate <= SInit;
   -- nstate
   case pstate is
      when SInit =>
           nstate <= SInit;
           if (A = '1') then
              nstate <= SRead;
           end if;
 
      when SRead =>
           nstate <= SRead;
      when others => null;
   end case;
end process;
 
--Output logic
output_logic: process(pstate)
begin
   -- default values
   Y <= '0';
 
   case pstate is
      when SRead =>
         Y <= '1';
      when others =>
         null;
   end case;
end process;

Definice signálů:

architecture behavioral of xxx is
  type FSMstate is (SInit, SRead);
  signal pstate : FSMstate; -- actual state
  signal nstate : FSMstate; -- next state
begin
  ...

Definice výstupní logiky a generování následujícího stavu lze bez problemů sloučit do jednoho procesu. Následující ukázka popisuje stejný automat.

--Present State registr
pstatereg: process(RST, CLK)
begin
   if (RST='1') then
      pstate <= SInit;
   elsif (CLK'event) and (CLK='1') then
      pstate <= nstate;
   end if;
end process;
 
--Next State logic, Output logic
nstate_logic: process(pstate, A)
begin
   -- default values
   nstate <= SInit;
   Y <= '0';
 
   case pstate is
      when SInit =>
           nstate <= SInit;
           if (A = '1') then
              nstate <= SRead;
           end if;
 
      when SRead =>
           nstate <= SRead;
           Y <= '1';
      when others => null;
   end case;
end process;
Zobrazeno: 22415x Naposledy: 29.11.2023 14:45:21