VHDL/Typy danych

Z Wikibooks, biblioteki wolnych podręczników.

W niniejszym rozdziale zostaną omówione podstawowe typy oraz atrybuty danych stosowanych w języku VHDL. Opisany także zostanie sposób deklarowania własnych typów.


Obiekty danych[edytuj]

Obiekt jest identyfikowalnym elementem języka VHDL, który posiada wartość określonego typu. Rozróżniamy trzy rodzaje obiektów: stałe, zmienne i sygnały. Sposób deklaracji i używania tych elementów jest praktycznie identyczny jak w innych językach.


Stałe[edytuj]

Stała jest takim obiektem, któremu przypisuje się wartość tylko raz - za pomocą inicjalizacji podczas deklarowania - i nie może być ona już później modyfikowana. Deklarowanie stałych wygląda następująco:

constant identyfikator_stałej : typ_stałej := wartość;

Stałe mogą być deklarowane w dowolnym obszarze deklaratywnym:

  • pakietu
  • interfejsu
  • architektury

To w jakim obszarze jest deklarowana stała decyduje o jej widoczności, a więc możliwość jej stosowania.


Zmienne[edytuj]

Zmienna jest obiektem, którego wartość może być inicjowana i/lub modyfikowana zawsze po zadeklarowaniu. Deklarowanie zmiennych wygląda następująco:

variable identyfikator_zmiennej : typ_zmiennej;

lub

variable identyfikator_zmiennej : typ_zmiennej := wartość_początkowa;

W tym ostatnim przypadku, zmienna została zainicjowana wartością wartość_początkowa. Zmienne mogą być stosowane tylko w procesach i podprogramach. Ponadto można je deklarować tylko w obszarach deklaratywnych procesu, lub podprogramu.

Syntezowalność zmiennych nie jest dobrze zestandaryzowana. Sposób i zakres, jest różny w zależności od producenta oprogramowania syntezy.


Sygnały[edytuj]

Sygnały są używane do połączenia modułu z całością. Deklarowanie sygnałów wygląda następująco:

signal identyfikator_sygnału : typ_sygnału;

lub

signal identyfikator_sygnału : typ_sygnału rodzaj_sygnału;

Drugi sposób deklarowania sygnałów pozwala na określenie - poza typem - rodzaju sygnału. Rozróżniamy dwa rodzaje: register i bus, co zostanie omówione w dalszej części książki.

Sygnał inicjuje się podobnie jak stałą czy zmienną (używając ":=" pod koniec deklaracji), jednak wtedy jest on niesyntezowalny.

Sygnały mogą być deklarowane w obszarze deklaratywnym:

  • interfejsu
  • architektury


Sygnały a zmienne[edytuj]

Różnice między tymi obiektami danych będą omawiane w dalszych częściach książki, jednak już teraz należy zaznaczyć główną różnicę:

  • sygnały są parami wartość i czasu w jakim sygnał posiada daną wartość,
  • zmienne reprezentują wartości danych abstrakcyjnych.

Aliasy[edytuj]

Alias jest to zastępczy identyfikator określonego obiektu lub jego części. Deklaruje się go poprzez zapis:

alias identyfikator_aliasu : typ_aliasu(zakres_aliasu) is identyfikator_obiektu(zakres_obiektu);

Podawanie w deklaracji zakresów jest opcjonalne, jednak odnosi się jedynie do obiektów złożonych (np. tablic). Zakresy muszą być równe, tzn. jeśli alias ma wskazywać na jakiś zakres_obiektu, np. od 31 do 24 elementu, to zakres_aliasu musi być określony jako od 7 do 0 elementu. Tak więc, aliasy często są stosowane do łatwiejszego operowania na tablicach. Przykładowo, jeśli istnieje sygnał:

 signal szyna_adresowa : std_logic_vector(27 downto 0);

to można zadeklarować aliasy:

 alias bity_młodsze : std_logic_vector(13 downto 0) is szyna_adresowa(13 downto 0);
 alias bity_starsze : std_logic_vector(13 downto 0) is szyna_adresowa(27 downto 14);

po to by mieć łatwiejszy dostęp do części młodszej i starszej szyny_adresowej.


Pliki[edytuj]

Ze względu na charakter obsługi plików, zostanie ona omówiona w jednym z dalszych rozdziałów.


Typy danych[edytuj]

Język VHDL posiada kilka wbudowanych (tzw. predefiniowanych) typów, zarówno prostych, jak i złożonych. Dodatkowo istnieje kilka standardowych podtypów, czyli kolejnych typów definiowanych na bazie innych typów. Język ten posiada również możliwość definiowania typów użytkownika. W dalszej części tego rozdziału niezbędna może okazać się ogólna wiedza dotycząca definiowania typów i różnicy pomiędzy terminami deklaracja a definicja.

Otóż, jak zapewne już wiesz z sekcji Obiekty danych, każdy obiekt musi mieć określony typ m.in. po to, aby wiadomo było jakie wartości może on przyjmować. Różne języki programowania mogą posiadać różną, właściwą dla siebie składnię definiowania typów. W języku VHDL składnia ta - w uogólnieniu - wygląda tak:

type identyfikator_typu is właściwości;

Zależnie od potrzeb, właściwości mogą być wyrażone w różnych formach, np. jako zakres (range) lub w innych wymienionych w dalszej części rozdziału.

Na przykładzie typów danych, pomiędzy deklaracją a definicją rozróżniamy zasadnicze cechy:

  • definicja określa jak wygląda dany typ: jakie może przyjmować wartości i w jakiej formie,
  • deklaracja powiadamia kompilator, jakiego typu będziemy używać w odniesieniu do danego obiektu.


Typy całkowite[edytuj]

Typ całkowity jest typem liczb całkowitych. Jedynym standardowo predefiniowanym typem całkowitym jest typ integer. Zakres wartości dla tego typu jest zależny od implementacji, ale standard gwarantuje, iż jest to co najmniej zakres od -231+1 do 231-1, tj. od -2147483647 do +2147483647. Przykładowa definicja typu całkowitego:

 type bajt is range 0 to 255;

Przykładowa deklaracja zmiennej typu integer:

 variable zmienna : integer := 123456;

Dodatkowo istnieją dwa predefiniowane podtypy:

 subtype natural is integer range 0 to największa_możliwa;

oraz

 subtype positive is integer range 1 to największa_możliwa;


Typy rzeczywiste[edytuj]

Typ rzeczywisty odzwierciedla (na miarę możliwości) liczby rzeczywiste. Istnieje tylko jeden predefiniowany typ rzeczywisty - real. Zakres wartości dla tego typu jest zależny od implementacji, ale standard gwarantuje, iż jest to co najmniej zakres od -1E38 do +1E38. Precyzja nie jest zdefiniowana przez standard, ale na pewno nie jest mniejsza niż 6 cyfr dziesiętnych. Przykładowa definicja typu rzeczywistego:

 type szansa is range 0.0 to 1.0;

Przykładowa deklaracja zmiennej typu real:

 variable zmienna : real := 12.345;

Typy wyliczeniowe[edytuj]

Wyliczenie jest uporządkowanym zbiorem znaków i/lub identyfikatorów. Typ wyliczeniowy jest określany przez wartości jakie może przyjmować obiekt tegoż typu. Definiuje się go w następujący sposób:

 type identyfikator_typu is (wartość1, wartość2, ... , wartośćN);

Istnieje kilka predefiniowanych typów wyliczeniowych:

 type severity_level is (note, warning, error, failure);
 
 type boolean is (false, true);
 
 type bit is ('0', '1');
 
 type character is (
    NUL,     SOH,     STX,     ETX,     EOT,     ENQ,     ACK,     BEL,
    BS,      HT,      LF,      VT,      FF,      CR,      SO,      SI,
    DLE,     DC1,     DC2,     DC3,     DC4,     NAK,     SYN,     ETB,
    CAN,     EM,      SUB,     ESC,     FSP,     GSP,     RSP,     USP,
    ' ',     '!',     '"',     '#',     '$',     '%',     '&',     ''',
    '(',     ')',     '*',     '+',     ',',     '-',     '.',     '/',
    '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',
    '8',     '9',     ':',     ';',     '<',     '=',     '>',     '?',
    '@',     'A',     'B',     'C',     'D',     'E',     'F',     'G',
    'H',     'I',     'J',     'K',     'L',     'M',     'N',     'O',
    'P',     'Q',     'R',     'S',     'T',     'U',     'V',     'W',
    'X',     'Y',     'Z',     '[',     '\',     ']',     '^',     '_',
    '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',
    'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
    'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
    'x',     'y',     'z',     '{',     '|',     '}',     '~',     DEL
 );

Przykładowa deklaracja zmiennej typu wyliczeniowego:

 variable zmienna : boolean := false;


Typy fizykalne[edytuj]

Typ fizykalny jest typem liczbowym przeznaczonym do reprezentowania pewnych wartości fizycznych takich, jak na przykład ciężar, odległość, czas czy napięcie. Definicja takiego typu zawiera jednostkę podstawową i ewentualnie kilka dodatkowych jednostek wraz z czynnikiem używanym do konwersji pomiędzy nimi. Przykładowa definicja typu fizykalnego:

 type odległość is range 0 to 1E9
    units
       um;
       mm = 1000 um;
       cm = 10 mm;
       m = 1000 mm;
    end units;

Aby odnieść się do danej jednostki, wystarczy napisać ją zaraz za liczbą, np:

 10 mm      1000 m

Istnieje jeden jedyny predefiniowany typ fizykalny, ale za to niezwykle ważny - time. Używany jest on głównie do określenia opóźnień podczas symulacji. Jego definicja wygląda następująco:

 type time is range zależne_od_implementacji
    units
       fs;
       ps = 1000 fs;
       ns = 1000 ps;
       us = 1000 ns;
       ms = 1000 us;
       sec = 1000 ms;
       min = 60 sec;
       hr = 60 min;
    end units;

Przykładowa deklaracja typu fizykalnego:

 variable zmienna : time := 1 hr 30 min;

Typy tablicowe[edytuj]

Jako tablicę w języku VHDL rozumie się zbiór indeksowanych elementów, z których każdy jest tego samego typu. Tablice mogą być jednowymiarowe (z jednym indeksem) lub wielowymiarowe (z wieloma indeksami). Indeksy mogą być dowolnego podtypu całkowitego i/lub mogą być ograniczone w definicji typu tablicowego. Przykładowe definicje takiego typu:

 type słowo is array (31 downto 0) of bit;

oznacza typ tablicowy słowo o 32. elementach typu bit indeksowanych w odwrotnym porządku, a definicja:

 type bank is array (byte range 1 to 170, 0 to 5) of real;

oznacza typ tablicowy bank o 170. elementach (od 1 do 170), z czego każdy zawiera 6. elementów (od 0 do 5) typu rzeczywistego. Zatem tablice tego typu będą wielowymiarowe (dokładnie dwuwymiarowe).

Dodatkowo istnieje możliwość definiowania tablic o nieokreślonym rozmiarze. Służą do tego puste nawiasy kątowe <>. W rzeczywistości rozmiar ten ogranicza jedynie pojemność typu całkowitego i trzeba go podać już przy deklaracji obiektu takiego typu. Na przykład:

 type mój_wektor is array (integer range <>) of character;
 variable zmienna : mój_wektor(1 to 9) := ('h', 'o', 'l', 'l', 'y', 'w', 'o', 'o', 'd');

W linii pierwszej definiuje się typ mój_wektor. W linii drugiej deklarowana jest zmienna o typie mój_wektor indeksowanym od 1 do 9. Zmienna ta jest inicjowana kolejnymi wymienionymi znakami: pierwszy podany znak ('h') zostanie przypisany pod najniższym indeksem (czyli w tej tablicy będzie to 1), drugi ('o') pod następnym indeksem (2), trzeci ('l') pod jeszcze następnym (3) itd. Tak więc, obiekt zmienna traktowany jako ciąg znaków posiadał będzie wartość hollywood. W wypadku tablic można również przypisywać (zamiast inicjować) wartości wielu indeksom naraz. Innym sposobem nadania odpowiednich wartości jest asocjacja pozycyjna:

 zmienna := (6 => 'w', 7 => 'o', 8 => 'o', 9 => 'd', 1 => 'h', 2 => 'o', 3 => 'l', 4 => 'l', 5 => 'y');

Tą metodą podawane wartości mogą być wymienione w dowolnym porządku, a ich indeks jest określony przez pozycję asocjacji. W wyniku zmienna przechowywać będzie ciąg (tablicę) znaków hollywood. Poza asocjacją pozycyjną stosować można również asocjację nazewniczą. Istnieje specjalne słowo kluczowe przeznaczone do takich zastosowań - others. Pozwala ono na przypisanie nie wymienionym pozycjom konkretnej wartości. Identyczny do powyższego wynik uzyskać można zatem tak:

 zmienna := ('h', 3 => 'l', 4 => 'l', 5 => 'y', 6 => 'w', 9 => 'd', others => 'o');

Istnieją dwa predefiniowane typy tablicowe:

 type string is array (positive range <>) of character;
 type bit_vector is array (natural range <>) of bit;

Typy rekordów[edytuj]

Rekord jest zbiorem identyfikowalnych elementów, z których każdy może być innego typu. Typ rekordu definiuje się następująco:

 type mój_rekord is
    record
       czas : time;
       koszty : integer;
       rodzaj : integer range 0 to 9;
    end record;

Przykładowa deklaracja obiektu tego typu:

 variable obiekt_rek : mój_rekord;

Aby przykładowo odczytać wartość pola koszty tego rekordu, należy odwołać się do niego po jego nazwie, czyli:

 inna_zmienna := obiekt_rek.koszty;

Teraz inna_zmienna przyjmie wartość pola koszty tego rekordu. Inne aspekty używania rekordów wyglądają analogicznie.

Podobnie jak dla tablic, polom rekordów można przypisywać różne wartości naraz (patrz Tablice) włączając w to obydwie odmiany asocjacji - pozycyjną i nazewniczą.


Podtypy[edytuj]

Podtypy są niczym innym jak typem bazującym na którymś z podstawowych typów. Ich ideą jest m.in. ograniczenie zakresu typu wyjściowego. Ich definicja wygląda podobnie, jak definicja zwykłych typów. Zasadniczą różnicą jest jednak stosowanie słowa kluczowego subtype zamiast type, na przykład:

 subtype licznik is integer range 0 to 200;
 subtype cyfry is character range'0' to '9';

Podtypy mogą być również wykorzystywane do definiowania odmian typów tablicowych, które wyjściowo miały nieokreślony rozmiar:

 subtype nazwa is string(1 to 8);
 subtype słowo is bit_vector(31 downto 0);


Biblioteki standardowe[edytuj]

Std_logic[edytuj]

Std_logic_vector[edytuj]