Język VHDL jest przeznaczony do tworzenia struktur układów cyfrowych. Zaczniemy więc od najprostszych "cegiełek" z których można zbudować działające urządzenie - bramek logicznych.
Na początku tworzymy nowy projekt (analogicznie do opisu z poprzedniego artykułu). Nie będziemy już ponownie opisywać całego procesu, aby nie rozciągać artykułu. W nowym projekcie tworzymy plik typu VHDL Module i przechodzimy do praktyki!
Bramka AND na FPGA
Bramka AND jest iloczynem logicznym. Stan logiczny wysoki wszystkich wejść daje sygnał wyjściowy wysoki. W każdym innym przypadku na wyjściu pojawi się stan logiczny niski (0).
Przykład tabeli prawdy dla bramki AND z dwoma wejściami widoczny jest poniżej. Szczegóły na temat samych funktorów logicznych (bramek) znaleźć można w kursie techniki cyfrowej oraz na naszych elektronicznych ściągach.
Stwórzmy teraz własną bramkę AND na FPGA. Wejściami będą dwa przyciski oznaczone na naszym zestawie (SW1 oraz SW2). Wyjściem będzie jedna z diod świecących. Cały układ będzie więc symbolicznie wyglądał następująco:
FPGA w roli bramki AND.
Przykładowy program realizujący to zadanie widoczny jest poniżej. Zacznijmy od jego omówienia:
gotowy plik VHDL dla projektu omawiającego bramki logiczne
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- dodajemy biblioteki
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
-- tu okreslamy jakie wejscia i wyjscia bedzie mial nasz uklad cyfrowy
entity podstawowe_bramki is
Port(Switch:inSTD_LOGIC_VECTOR(1downto0);-- dwa przyciski jako wejscia bramki logicznej
LED:outSTD_LOGIC_VECTOR(0downto0));-- dioda led jako wyjscie naszej bramki logicznej
-- zwroc UWAGE! na dodatkowy nawias zamykajacy blok Port
-- przed srednikiem!
endpodstawowe_bramki;
-- slowo "Behavioral" odnosi sie do stopnia abstrakcji opisu naszej architektury
-- nie musisz sie nim na tym etapie zbytnio przejmowac
-- zagadnienie to obejmuje bardziej zaawansowane elementy tworzenia aplikacji w VHDL
architecture Behavioral ofpodstawowe_bramki is
begin
-- opis dzialania naszej aplikacji zawiera sie w ponizszej instrukcji;
LED(0)<=Switch(0)andSwitch(1);
endBehavioral;
Najpierw dodajemy biblioteki: library IEEE umożliwia nam skorzystanie ze standardowych konstrukcji VHDL takich jak entity/architecture itd. Linijka use IEEE.STD_LOGIC_1164.ALL; umożliwi nam skorzystanie z typów std_logic oraz std_logic_vector (więcej na temat tych typów w dalszej części artykułu).
Instrukcje dodające biblioteki w kodzie VHDL
1
2
3
-- dodajemy biblioteki
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
Aby zaimplementować bramkę logiczną AND w VHDL struktura bloku entity powinna wyglądać następująco (dokładnie blok ten omówimy przy następnym projekcie). Mówiąc w skrócie w tym miejscu określamy wejścia i wyjścia budowanego układu:
Blok entity
1
2
3
4
5
6
7
8
-- tu okreslamy jakie wejscia i wyjscia bedzie mial nasz uklad cyfrowy
entity podstawowe_bramki is
Port(Switch:inSTD_LOGIC_VECTOR(1downto0);-- dwa przyciski jako wejscia bramki logicznej
LED:outSTD_LOGIC_VECTOR(0downto0));-- dioda led jako wyjscie naszej bramki logicznej
-- zwroc UWAGE! na dodatkowy nawias zamykajacy blok Port
-- przed srednikiem!
endpodstawowe_bramki;
W bloku architecture między słowami kluczowymi begin/end opisujemy działanie układu. W tym wypadku interesuje nas, aby na LED(0) ustawić wynik operacji Switch(0) and Switch(1).
Intstrukcja realizująca bramkę AND
1
LED(0)<=Switch(0)andSwitch(1);
Numeracja diod i przycisków nie jest powiązana z fizycznymi elementami na płytce (za to odpowiada będzie plik ucf)!
Aby przetestować bramkę należy utworzyć plik .ucf. Plik ten opisuje połączenia naszych nazw z bloku entity do odpowiednich pinów FPGA. Podobnie jak w poprzedniej części kursu są dwie drogi. Pierwsza, to użycie gotowego pliku - elbertv2_bramki.ucf (poprzez Add source...).
Drugi sposób, to samodzielne, ręczne utworzenie ucf'a. Wtedy tworzymy plik typu Implementation Constraints File. W jego wnętrzu odpowiednio opisujemy połączenia:
W przypadku przycisków należy dodać komendę "| PULLUP". Wynika to z faktu, że na naszej płytce ElbertV2 przyciski (Switche) są połączone z masą (GND) bez dodatkowych rezystorów podciągających. Wycinek z schematu używanego zestawu:
Fragment schematu używanego zestawu.
Poniżej widać, że przyciski, których używamy podłączone są do pinów 80 i 79:
Fragment schematu używanego zestawu.
Fragment schematu używanego zestawu.
Gdy wszystko jest gotowe można przeprowadzić syntezę kodu w Xilinx ISE i sprawdzić układ w praktyce! Należy pamiętać, aby we właściwościach opcji Generate Programming File zaznaczyć Create Binary Configuration File. Proces generowanie i wgrywania plików na płytkę został dokładnie opisany w poprzedniej części kursu.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Zestaw uruchomieniowy Elbert v2 - Spartan 3A z wszystkimi niezbędnymi peryferiami do wykonania ćwiczeń z kursu FPGA!
Masz już zestaw? Zarejestruj go wykorzystując dołączony do niego kod. Szczegóły »
Program wgrany i... jakie spostrzeżenia? Pamiętaj, że przyciski z których korzystamy jako wejście to SW1 i SW2. Układ nie działa tak, jak się tego spodziewaliśmy!
Błędne działania bramki AND - animacja.
Układ działa jak zanegowana bramka OR (czyli NOR)! Przyjmując, że wciśnięty przycisk dawałby jedynkę logiczną to działanie układu odpowiada właśnie bramce NOR. My jednak chcieliśmy zaimplementować bramkę AND, skąd więc taki wynik? Okazuje się, że wszystko jest w porządku, jedynie nasze przyciski działają według logiki odwrotnej.
Gdy przycisk nie jest wciśnięty, to na wejściu
układu FPGA jest to widziane jako jedynka logiczna.
Aby doświadczyć poprawnego działania bramki AND zgodnie z intuicyjnym sposobem testowania układu (wciśnięcie przycisku = logiczne 1), należy w bloku Architecture zanegować wejścia:
Intstrukcja realizująca bramkę AND
1
LED(0)<=(notSwitch(0))and(notSwitch(1));
Od teraz układ będzie działał zgodnie z naszą intuicją - wciśnięcie przycisku będzie oznaczało stan wysoki (logiczne 1). Po ponownym przeprowadzeniu procesu syntezy i wgraniu konfiguracji wszystko powinno działać zgodnie z przyjętymi założeniami.
Poprawne działanie bramki AND w praktyce.
Ostateczny, poprawny kod widoczny jest poniżej:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- dodajemy biblioteki
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
-- tu okreslamy jakie wejscia i wyjscia bedzie mial nasz uklad cyfrowy
entity podstawowe_bramki is
Port(Switch:inSTD_LOGIC_VECTOR(1downto0);-- dwa przyciski jako wejscia bramki logicznej
LED:outSTD_LOGIC_VECTOR(0downto0));-- dioda led jako wyjscie naszej bramki logicznej
-- zwroc UWAGE! na dodatkowy nawias zamykajacy blok Port
-- przed srednikiem!
endpodstawowe_bramki;
-- slowo "Behavioral" odnosi sie do stopnia abstrakcji opisu naszej architektury
-- nie musisz sie nim na tym etapie zbytnio przejmowac
-- zagadnienie to obejmuje bardziej zaawansowane elementy tworzenia aplikacji w VHDL
architecture Behavioral ofpodstawowe_bramki is
begin
-- opis dzialania naszej aplikacji zawiera sie w ponizszej instrukcji;
LED(0)<=(notSwitch(0))and(notSwitch(1));
endBehavioral;
Inne bramki logiczne
W poniższej tabeli znajduje się wykaz wszystkich operatorów logicznych w języku VHDL. Warto we własnym zakresie przećwiczyć tworzenie innych bramek w praktyce! Jest to podstawa języka VHDL, którą warto opanować do sprawnego korzystania z FPGA.
W jednej instrukcji można połączyć kilka operatorów logicznych!
Przykładowo, aby sprawdzić działanie bramki OR na FPGA w bloku Architecture wpisujemy:
Intstrukcja realizująca złożoną funkcje logiczną
1
LED(0)<=((notSwitch(0))or(notSwitch(1)));
Cały kod w VHDL powinien wyglądać wtedy następująco:
gotowy plik VHDL dla projektu omawiającego bramki logiczne
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- dodajemy biblioteki
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
-- tu okreslamy jakie wejscia i wyjscia bedzie mial nasz uklad cyfrowy
entity podstawowe_bramki is
Port(Switch:inSTD_LOGIC_VECTOR(1downto0);-- dwa przyciski jako wejscia bramki logicznej
LED:outSTD_LOGIC_VECTOR(0downto0));-- dioda led jako wyjscie naszej bramki logicznej
-- zwroc UWAGE! na dodatkowy nawias zamykajacy blok Port
-- przed srednikiem!
endpodstawowe_bramki;
-- slowo "Behavioral" odnosi sie do stopnia abstrakcji opisu naszej architektury,
-- nie musisz sie nim na tym etapie zbytnio przejmowac;
-- zagadnienie to obejmuje bardziej zaawansowane elementy tworzenia aplikacji w VHDL;
architecture Behavioral ofpodstawowe_bramki is
begin
-- opis dzialania naszej aplikacji zawiera sie w ponizszej instrukcji;
LED(0)<=((notSwitch(0))or(notSwitch(1)));
endBehavioral;
Poprawne działanie bramki OR:
Bramka OR w praktyce.
Czym jest multiplekser?
Multiplekser jest popularnym układem w technice cyfrowej. Jego działanie polega na tym, że z liczby N sygnałów wejściowych na wyjściu przekazywany jest stan logiczny tylko jednego z nich. O tym które z wejść będzie widziane na wyjściu decydują dodatkowe wejścia adresowe.
Multiplekser i adresacja – zasada ogólna.
Przykład adresacji multipleksera.
Mówiąc prościej: mamy kilka numerowanych wejść i jedno wyjście. Dodatkowo występują linie adresowe, na których ustawiamy numer wejścia, który ma być przekazany na wyjście. Adresacja dokonywana jest za pomocą binarnego systemu liczbowego. Przykład na adresie 2 bitowym:
00 w systemie binarny, oznaczać będzie wejście nr 0,
01 w systemie binarny, oznaczać będzie wejście nr 1,
10 w systemie binarny, oznaczać będzie wejście nr 2,
11 w systemie binarny, oznaczać będzie wejście nr 3.
Działanie tego układu w praktyce widoczne jest na poniższej animacji:
Wybór wejścia w multiplekserze - animacja.
Multipleksery wykorzystuje się w systemach, gdzie jest zapotrzebowanie na zaoszczędzenie liczby połączeń, którymi przesyłane są sygnały. Maksymalna liczba wejść danych multipleksera adresowanych poprzez linie adresowe wyraża się wzorem N=2^A, gdzie: A - liczba wejść adresowych, N - liczba wejść danych.
Przykładowo, za pomocą dwóch linii adresowych możemy wystawić na wyjściu układu jeden z 4 sygnałów wejściowych.
Implementacja multipleksera w VHDL
Pora uruchomić taki układ na FPGA. Załóżmy, że wejściami naszego multipleksera będą cztery przyciski SW1, SW2, SW3, SW4. Za pomocą przełączników suwakowych DIP-switch będziemy ustawiać adres wejścia, które ma być przekazane na wyjście, czyli diodę świecącą.
Uwaga! Przyciski na płytce numerowane są od 1 (SW1, SW2, SW3, SW4), ale dla potrzeb eksperymentu z multiplekserem będziemy numerować je od 0 (wejście nr 0, 1, 2, 3). To tylko uproszczenie, które ułatwi zrozumienie działania układu.
Opis przycisków używanych do testu multipleksera.
Program realizujący to zadanie widoczny jest poniżej:
Kod realizujacy funkcje multipleksera
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- dolaczamy biblioteki
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
-- w bloku entity definiujemy wejscia i wyjscia naszej aplikacji
entity multiplekser is
port(Switch:inSTD_LOGIC_VECTOR(3downto0);-- to linia wejsc danych naszego multipleksera
DPSwitch:inSTD_LOGIC_VECTOR(1downto0);-- sygnaly decydujace ktore wejscie jest przekazywane na wyjscie
--(sygnaly adresowe tez sa wejsciami naszego ukladu)
LED:outSTD_LOGIC_VECTOR(0downto0));-- tu jest okreslone wyjscie naszego ukladu
endmultiplekser;
architecture Behavioral ofmultiplekser is
-- ponizej deklaracje sygnalow lokalnych (sa wykorzystywane w konstrukcji with select)
signal szyna_buforowa_wejsc:STD_LOGIC_VECTOR(3downto0);
signal szyna_buforowa_adresow:STD_LOGIC_VECTOR(1downto0);
begin
withszyna_buforowa_adresow select-- stan sygnalow adresowych decyduje o wyborze rodzaju wejscia przekazywanego na wejscie
LED(0)<=szyna_buforowa_wejsc(0)when"00",
szyna_buforowa_wejsc(1)when"01",
szyna_buforowa_wejsc(2)when"10",
szyna_buforowa_wejsc(3)whenothers;-- w ostatnim punkcie musi byc srednik!
-- ponizsze instrukcje sa wykonywane rownoczesnie z konstrukcja "with select"
szyna_buforowa_wejsc<=notSwitch;-- na raz jest przeprowadzana negacja wszystkich bitow danej zmiennej
szyna_buforowa_adresow<=notDPSwitch;
endBehavioral;
Zajmijmy się teraz analizą powyższego kodu. Zacznijmy od samej góry:
blok entity multipleksera
1
2
3
4
5
6
7
-- w bloku entity definiujemy wejscia i wyjscia naszej aplikacji
entity multiplekser is
port(Switch:inSTD_LOGIC_VECTOR(3downto0);-- to linia wejsc danych naszego multipleksera
DPSwitch:inSTD_LOGIC_VECTOR(1downto0);-- sygnaly decydujace ktore wejscie jest przekazywane na wyjscie
--(sygnaly adresowe tez sa wejsciami naszego ukladu)
LED:outSTD_LOGIC_VECTOR(0downto0));-- tu jest okreslone wyjscie naszego ukladu
endmultiplekser;
Wektory w VHDL
W bloku entity deklarujemy pewne typy sygnałów. Są to magistrale (nazywane również "bus"), a w języku VHDL określa się je mianem "vector" (wektor). Sygnał tego typu jest połączeniem określonej liczby bitów. Prawidłowa deklaracja wygląda jak na poniższym schemacie:
Nasz przykład z powyższego programu jest następujący:
przykład deklaracji sygnału (wejściowego) jako magistrali bitów
1
Switch:inSTD_LOGIC_VECTOR(3downto0);-- to linia wejsc naszego multipleksera
Wektor ten nosi nazwę Switch i jest kierunku wejściowego (jest traktowany jako wejście dla naszego FPGA), ma długość 4 bitów. Jego najstarszy bit (patrząc od lewej strony) ma numer 3.
W praktyce oznacza to, że na mamy
do czynienia z czterema pinami wejściowymi multipleksera.
Język VHDL daje możliwość odwrotnego numerowania bitów w wektorze. W takim przypadku najstarsze bity posiadają najmniejsze numery. By to osiągnąć należy w nawiasie zamiast downto wpisać to. Bardzo rzadko korzysta się z tego rodzaju deklaracji i nie warto robić sobie takich nawyków. Może to ograniczyć czytelność kodu i tym samym utrudnić analizę błędów.
Sygnały lokalne
Na początku bloku "architecture" umieszczamy deklarację sygnałów lokalnych.
deklaracja sygnałów lokalnych
1
2
3
-- ponizej deklaracje sygnalow lokalnych (sa wykorzystywane w konstrukcji with select)
signal szyna_buforowa_wejsc:STD_LOGIC_VECTOR(3downto0);
signal szyna_buforowa_adresow:STD_LOGIC_VECTOR(1downto0);
Sygnały te nie są wyprowadzane na zewnątrz chipu FPGA. Są one używane jedynie jako zmienne pomocnicze. Sygnały lokalne deklaruje się miedzy architecture i begin. Przykładem sygnału lokalnego jest "licznik" wykorzystany w poprzedniej części kursu. Sygnał ten służył wtedy do zapamiętania stanu licznika.
Sygnały lokalne deklaruje się podobnie jak sygnały zewnętrzne z bloku Port w danym entity, jednak z następującymi różnicami:
deklaracje należy poprzedzić dyrektywą signal,
po dwukropku nie określa się kierunku.
Konstrukcja with-select
Za słowem begin znajduje się blok konstrukcji with-select. Umożliwia ona wybór przypisania sygnału wyjściowego (bądź sygnału lokalnego) do jednej z możliwości wyróżnionych komendą when. Wybór jest uzależniony od wartości sygnału przełączającego umieszczonego między with, a select w pierwszej linijce.
Dla osób zaznajomionych z programowaniem konstrukcja
ta powinna się kojarzyć z analogicznym switch-case.
konstrukcja with-select w aplikacji multipleksera
1
2
3
4
5
withszyna_buforowa_adresow select-- stan sygnal adresowego decyduje o wyborze rodzaju wejscia przekazywanego na wejscie
LED(0)<=szyna_buforowa_wejsc(0)when"00",
szyna_buforowa_wejsc(1)when"01",
szyna_buforowa_wejsc(2)when"10",
szyna_buforowa_wejsc(3)whenothers;-- w ostatnim punkcie musi byc srednik!
W naszym przykładzie działanie tego mechanizmu wygląda następująco:
gdy stan logiczny sygnału "szyna_buforowa_adresow" będzie "00" to na wyjściu naszego multipleksera będzie wystawiony sygnał z wejścia "szyna_buforowa_wejsc(0)".
Generalnie nie ma potrzeby podawania wszystkich kombinacji możliwych dla przełącznika w instrukcjach when, wartości pozostałe wystarczy opisać komendą others jak w ostatniej linijce.
Ze względu na swoją strukturę konstrukcja with-select jest też określana jako tabela prawdy (ang. truth table). Nazwa pochodzi ze względu na podobieństwo to tablic opisujących działanie układów cyfrowych zależnie od kombinacji wejść.
Przypisanie wektorów wejściowych do sygnałów lokalnych
W ostatnim bloku przypisujemy sygnały (wektory) wejściowe do sygnałów lokalnych. Operacja ta jest połączona z równoczesną negacją wszystkich bitów danego wektora. Ma to na celu przywrócenie intuicyjności działania multipleksera, tak aby po wciśnięciu przyciski były rozumiane przez "tabelę prawdy" w konstrukcji "with-select" jako logiczne 1.
przypisanie zanegowanych syg wejsciowych do syg lokalnych
1
2
3
-- ponizsze instrukcje sa wykonywane rownoczesnie z konstrukcja "with select"
szyna_buforowa_wejsc<=notSwitch;-- na raz jest przeprowadzana negacja wszystkich bitow danej zmiennej
szyna_buforowa_adresow<=notDPSwitch;
Na koniec jest jeszcze jedna rzecz wymagająca pewnego wyjaśnienia. W naszym przykładzie konstrukcja with-select oraz przypisania do wektorów buforujących wykonywane są równocześnie. Nie ma tu żadnego znaczenia kolejność tych bloków kodu między sobą. Nie ma też żadnego opóźnienia między przypisaniem do sygnałów buforujących, a ich użyciem w konstrukcji with-select.
Tworzenie pliku ucf dla multipleksera
Ostatnim krokiem jest przygotowanie pliku ucf. Tworzymy więc plik typu "Implementation Constraints File" i uzupełniamy go poniższą zawartością:
Działanie multipleksera widoczne jest w praktyce na poniższej animacji. Gdy na przełącznikach przesuwanych ustawiony jest adres 00, to na diodzie reprezentowany jest sygnał z wejścia 0. Inaczej mówiąc wciśnięcie tego przycisku spowoduje włączenie diody (pozostałe przyciski nie powodują żadnych zmian). Po wybraniu adresu 01 do diody będzie przepływał sygnał od wejścia numer 1 itd.
Działanie układu z multiplekserem w praktyce - animacja.
Podsumowanie
Po tych kilki przykładach wiele powinno się rozjaśnić. Warto poświęcić teraz trochę czasu na eksperymentowanie z innymi bramkami logicznymi. Oczywiście nie trzeba do każdej tworzyć osobnego programu - można spokojnie testować po kilka bramek jednocześnie (w końcu mamy 6 przycisków i 8 diod)! Dobrym treningiem będzie również na pewno rozbudowanie multipleksera, aby obsługiwał więcej wejść.
Autor kursu: Adam Bemski Redakcja: Damian Szymański Testy, ilustracje: Piotr Adamczyk
O autorze: Adam Bemski
Autorem kursu jest Adam Bemski, specjalista od systemow wbudowanych. Pracuje w obszarze automatycznego testowania urządzeń z funkcjonalnością IoT. Adam dodatkowo prowadzi zajęcia z techniki mikroprocesorowej na wyższej uczelni DHBW Stuttgart. Więcej szczegółów o Adamie na blogu adambemski.com.
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY na bazie Arduino i Raspberry Pi.
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY z Arduino i RPi.
Trwa ładowanie komentarzy...