Po instalacji sterowników i środowiska możemy wgrać pierwszy program. Pozwoli to na proste sprawdzenie, czy wszystko zostało odpowiednio zainstalowane i skonfigurowane.
W tej części kursu FPGA zajmiemy się wgraniem gotowego programu. Dzięki czemu przejdziemy szybko przez wszystkie etapy tworzenia projektu.
Tworzenie pierwszego projektu w Xilinx ISE WebPack
Pora na wykonanie czegoś praktycznego. Od teraz zestaw Elbert v2 będzie już niezbędny. Na początek "Hello World" świata elektronicznego, czyli "mryganie" diodami świecącymi.
Krok 1. Na początek uruchamiamy "Xilinx ISE WebPack":
Xilinx ISE Webpack - pierwsze uruchomienie.
Krok 2. W menu głównym wybieramy File → New Project. Otworzy się okno kreatora nowego projektu. W polu Top-level source type wskazujemy opcję HDL. Wybieramy nazwę i lokalizację dla nowego projektu i klikamy Next.
W polu z nazwą projektu należy unikać polskich znaków!
Xilinx ISE Webpack - tworzenie nowego projektu.
Krok 3. Pojawi się okno ustawień dla projektu. Jest ono bardzo ważne, tutaj trzeba określić typ układu FPGA. Kluczowe jest wypełnienie pól Family, Device, Speed.
Poprawne ustawienia widoczne są na poniższym zrzucie ekranu:
Xilinx ISE Webpack - tworzenie nowego projektu.
Następnie klikamy Next. W tym momencie wyświetlone zostanie podsumowanie projektu, które powinno wyglądać następująco:
Masz już zestaw? Zarejestruj go wykorzystując dołączony do niego kod. Szczegóły »
Krok 4. Struktura projektu widoczna jest w lewym górnym rogu. Klikamy prawym przyciskiem myszy na symbolu układu xc3s50a-5tq144 i wybieramy New Source.
Tworzenie modułu VHDL w projekcie.
Krok 5. Pojawi się okno wyboru rodzaju pliku jaki ma być utworzony. Wybieramy VHDL Module. W polu File name można wpisać dowolną nazwę (bez polskich znaków). Należy się upewnić, że opcja Add to project jest zaznaczona. Następnie klikamy przycisk Next.
Tworzenie modułu VHDL w projekcie.
Krok 6. W tym momencie pojawi się kreator definiowania pinów dla naszej aplikacji (dokładniej mówiąc modułu VHDL). W tworzeniu tego pierwszego projektu nie będziemy go używać. W ten sposób nauczymy się lepiej składni VHDL.
Pozostawiamy wszystkie pola według ustawień domyślnych!
Tworzenie modułu VHDL w projekcie.
Krok 7. Teraz pokaże się podsumowanie kreatora naszego modułu. Przykładowa zawartość:
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;
-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;
entity mryganie_led is
endmryganie_led;
architecture Behavioral ofmryganie_led is
begin
endBehavioral;
Wielkość tekstu w edytorze można zmieniać skrótem:
CTRL + kręcenie rolką myszki.
Omówienie wygenerowanego kodu
Poniżej znajduje się skrótowe omówienie kodu. Do dokładnych wyjaśnień przejdziemy w dalszych częściach kursu. Na ten moment głównym celem jest ogólne przedstawienie struktury programu i jego przetestowanie w praktyce!
W języku VHDL każda linia zaczynająca się od dwóch znaków minusa ("--") traktowana jest jako komentarz i nie jest uwzględniana w procesie syntezy.
komentarz w VHDL
1
-- to jest komentarz
W stworzonym pliku z kodem VHDL na samej górze (w komentarzach) ujęto informacje, które często pojawiają się w bardziej rozbudowanych programach. Znajdziemy tam datę utworzenia projektu oraz jego twórców.
W naszym przypadku (pierwszy, prosty program)
można się spokojnie pozbyć tych komentarzy.
Poniżej tego opisu znajdziemy kilka linii, które dołączają biblioteki. To dzięki nim możliwe jest użycie pewnych zdefiniowanych typów. Zagadnienie to będzie później dokładniej wyjaśnione.
1
2
3
4
5
6
7
8
9
10
11
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;
-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;
W dalszej części tego przykładu będziemy używać operacji arytmetycznych, więc zgodnie z informacją w komentarzu musimy odkomentować linię use IEEE.NUMERIC_STD.ALL. Inne informacje z tej sekcji są zbędne więc możemy (ale nie musimy) okroić ją do poniższej wersji:
Dołączanie bibliotek w VHDL
1
2
3
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
useIEEE.NUMERIC_STD.ALL;
Poniżej umieszczona jest komenda entity (ang. jednostka). Entity określa pojedynczy układ. W tym miejscu trzeba się odwołać do koncepcji języka VHDL. Nasze entity opisuje parametry (wyprowadzenia) widziane z zewnątrz danego modułu VHDL. W jednym chipie FPGA może być skonfigurowane kilka zupełnie od siebie oddzielnych entity.
Pojęcia entity i moduł VHDL będą używane w tym kursie zamiennie!
W języku VHDL wejścia i wyjścia modułu VHDL opisuje się w bloku Port. Wewnątrz deklaruje się konkretne funkcje pinów. Drugim sformułowaniem, które określa moduł jest architecture. Określa ono wewnętrzne funkcjonowanie danego układu.
Poprzez analogię: Port to okładka, a architecture to wnętrze książki (entity).
W pierwszym projekcie wstawmy tam następujący fragment kodu (krótkie wyjaśnienia zawarte są w komentarzach):
zawartość bloku entity w pierwszym projekcie VHDL
1
2
3
4
5
6
entity mryganie_led is
Port(
Clk:inSTD_LOGIC;-- deklaracja, ze bedziemy korzystac z syg zegarowego jako wejscie
LED:outSTD_LOGIC_VECTOR(7DOWNTO0)-- zestaw diod led jako wyjscie !!! tutaj w ostatniej deklaracji nie ma srednika!!!
);
endmryganie_led;
W języku VHDL generalnie każdą "instrukcje" należy zakończyć średnikiem. Jednak częstym błędem jest dodawanie średnika za ostatnią deklaracją sygnału zewnętrznego w bloku "Port".
Jak widać blok entity otrzymał nazwę zgodną z nazwą pliku (modułu) VHDL, który wcześniej utworzyliśmy w środowisku Xilinx ISE. W tym przypadku jest to mryganie_led. W bloku Port następuje deklaracja połączeń zewnętrznych (wyprowadzeń) dla naszego FPGA. Jak widać w naszej pierwszej aplikacji będzie to sygnał zegarowy (jako wejście) i sygnał o szerokości 8 bitów dla diod świecących (jako wyjście).
Nasza aplikacja ma powodować miganie LEDów. Zrealizujemy to w postaci układu liczącego - timera, który po określonej liczbie cykli sygnału zegarowego będzie powodował zmianę stanu diod. Sygnał zegarowy jest dostępny na płytce ElbertV2 - ma on częstotliwość 12 MHz. Podobnie sprawa ma się z diodami LED - są one dostępne na naszym zestawie ElbertV2.
Na tym etapie jedynie deklarujemy jakie sygnały będą wykorzystywane w aplikacji. Przypisaniem do konkretnych wyprowadzeń na płytce zajmiemy się później.
Każdy sygnał musi mieć zdefiniowany typ. W naszym przypadku jest to standard logic zapisywany jako STD_LOGIC. Typ STD_LOGIC jest ogólnym typem zalecanym standardowo przy pracy z układami PLD. W tym momencie nie będziemy się zbytnio rozwodzić nad szczegółami, aby móc szybko uruchomić pierwszy przykład.
Dobre praktyki języka VHDL
Język VHDL nie jest "case-sensitive" - nie odróżnia wielkich i małych liter. Mimo to zaleca się następujące konwencje:
Słowa kluczowe VHDL zapisuje się małymi literami np.: "entity".
Własne definicje (np. stałych wartości) zapisuje się wielkimi literami.
Nazwy i oznaczenia muszą zaczynać się od liter (potem mogą być znaki, cyfry i podkreślniki). Nie należy przekraczać długości 32 znaków dla nazw.
Kompletne instrukcje VHDL są zakończone średnikiem. Do oddzielenia instrukcji służy przecinek lub dwukropek.
W dalszej części pliku VHDL Xylinx ISE umieścił automatycznie blok architecture o domyślnej nazwie Behavioral przypisanej do entity o nazwie mryganie_led.
Uwaga! Każdemu entity musi być przypisana minimum jedna architecture!
W tym miejscu wstawiamy poniższy kod (krótkie wyjaśnienia znajdują się w komentarzu):
zawartość bloku architecture
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
architecture Behavioral ofmryganie_led is
constantLICZNIK_LIMIT:integer:=5000000;-- deklaracja stalej z max wart licznika
signal licznik:unsigned(24downto0);-- definicja syg lokalnego
-- zdefiniowalismy zmienna o szerokosci 25 bitow
signal mryganie:STD_LOGIC_VECTOR(7DOWNTO0):="00000000";-- zmienna lokalna o szerokosci 8 bit
begin
process(Clk)-- blok proces - szczegolowe wyjasnienia beda zawarte w dalszej czesci kurs
Dokładniej mówiąc całą istotę działania danej architecture opisuje się pomiędzy begin, a end nazwa_architektury. W naszym bloku "architecture" przed "begin" zostały zawarte deklaracje sygnałów wewnętrznych.
Omówimy je teraz pobieżnie, szczegóły składni
będą wyjaśnione w dalszych częściach kursu.
Pierwsza linia, to deklaracja stałej - będzie to maksymalna wartość licznika, którego wyjście będzie zmieniało stan diod świecących.
deklaracja stałej - wartości licznika
1
constantLICZNIK_LIMIT:integer:=5000000;-- deklaracja stalej z max wart licznika
Kolejną instrukcją jest deklaracja sygnału lokalnego o nazwie licznik. Zmienna ta jest typu bez znaku i ma długość 25 bitów.
deklaracja sygnalow lokalnych naszej architecture
1
2
signal licznik:unsigned(24downto0);-- definicja syg lokalnego
-- zdefiniowalismy zmienna o szerokosci 25 bitow
Następnie deklarujemy sygnał mryganie, służy on jako bufor. Jego stan logiczny jest negowany przy każdym napełnieniu licznika. Musimy użyć bufora, gdyż wyjścia danego entity (w naszym przypadku chodzi o led), nie mogą być zmieniane w bloku process.
deklaracja sygnalow lokalnych naszej architecture
1
signal mryganie:STD_LOGIC_VECTOR(7DOWNTO0):="00000000";-- zmienna lokalna o szerokosci 8 bit
Kolejnym blokiem wewnątrz naszej architecture jest już to, co opisuje zachowanie naszej aplikacji i jest zawarte za instrukcją begin. Tutaj umieszczony został blok Process.
Warto zwrócić uwagę, że ten blok również
ma swoją oddzielną komendę begin!
blok architecture pierwszej aplikacji VHDL
1
2
3
4
5
6
7
8
9
10
11
12
13
process(Clk)-- blok proces - szczegolowe wyjasnienia beda zawarte w dalszej czesci kurs
mryganie<=notmryganie;-- zmien stan wszystkich bitow sygnalu mryganie na przeciwny
else-- ponizszy kod wykona sie gdy wart licznika nie osiagnela wart max
licznik<=licznik+1;-- zwieksz wart licznika o jeden (inkrementacja)
endif;
endif;
endprocess;
W dalszej części kursu omówimy dokładniej jego składnię. Na ten warto jedynie wiedzieć, że odbywa się tutaj inkrementacja (zwiększanie wartości o jeden). Inkrementowany jest sygnał licznik do wartości zadeklarowanej jako licznik_limit.
Gdy licznik osiągnie maksymalną wartość następuje zmiana wartości sygnału mryganie na przeciwny. Umożliwia to włączanie i wyłączenie diod święcących.
Na końcu przekazujemy wartości sygnału lokalnego na piny zewnętrzne. Odbywa się to tutaj:
przypisanie wartości bufora mryganie do wyjścia led
1
LED<=mryganie;-- przypisujemy wartosc sygnalu mryganie do wyjscia led (zadeklarowanego w bloku Port naszego entity);
Powyższa instrukcja musi być zawarta przed
słowem kluczowym end danej architecture.
Na ten moment zawartość pliku (modułu) VHDL powinna wyglądać następująco:
zawartość pliku VHDL dla pierwszej aplikacji: mryganie_led
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
33
34
35
36
37
38
library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
useIEEE.NUMERIC_STD.ALL;
entity mryganie_led is
Port(
Clk:inSTD_LOGIC;-- deklaracja, ze bedziemy korzystac z syg zegarowego jako wejscie
LED:outSTD_LOGIC_VECTOR(7DOWNTO0)-- zestaw diod led jako wyjscie !!! tutaj w ostatniej deklaracji nie ma srednika!!!
);
endmryganie_led;
architecture Behavioral ofmryganie_led is
constantLICZNIK_LIMIT:integer:=5000000;-- deklaracja stalej z max wart licznika
signal licznik:unsigned(24downto0);-- definicja syg lokalnego
-- zdefiniowalismy zmienna o szerokosci 25 bitow
signal mryganie:STD_LOGIC_VECTOR(7DOWNTO0):="00000000";-- zmienna lokalna o szerokosci 8 bit
begin
process(Clk)-- blok proces - szczegolowe wyjasnienia beda zawarte w dalszej czesci kurs
Teraz zajmiemy się stworzeniem pliku .ucf (user constraint file). Jest on niezbędny, aby przypisać fizyczne sygnały (piny FPGA) nazwom stworzonym wewnątrz bloku Port naszego entity.
Uwaga! Operacje na pliku ucf są objęte pewnym ryzykiem. Błąd w przypisaniach odpowiednich pinów może prowadzić do uszkodzenia zestawu ElbertV2. Jeśli chcesz uniknąć takiej sytuacji możesz dodać gotowy plik elbertv2_mryganie.ucf.
Błąd w wypełnieniu pliku może prowadzić do zwarcia, np. gdy pin do którego na zestawie ElbertV2 podłączony jest przycisk przypiszemy jako wyjście diody. Nasz przycisk zwiera potencjał do masy, więc gdy będziemy w kodzie wymuszać na tym pinie jedynkę logiczną - nastąpi zwarcie i zniszczenie FPGA. Podobnych zagrożeń jest więcej - należy uważać!
Dodawanie gotowego pliku ucf
Krok 1. W tym celu klikamy prawym przyciskiem myszy na pliku VHDL w Xilinx ISE jak na poniższym zrzucie ekrany i wybieramy Add Source. W nowo otwartym oknie wybieramy plik elbertv2_mryganie.ucf (najlepiej zapisać go wcześniej w folderze z projektem). Klikamy OK.
dodawanie gotowego pliku ucf.
Krok 2. Jeśli wszystko poszło dobrze, to pojawi się poniższe okno:
Dodawanie gotowego pliku .ucf.
Należy się upewnić, że są zaznaczone opcje Implementation w kategorii Association oraz work w Library. Klikamy przycisk OK. To wystarczy by plik .ucf był dodany do projektu!
Samodzielne tworzenie pliku ucf
Tworzenie własnego pliku ucf, to nic strasznego - należy jednak zachować szczególną uwagę, bo prosty błąd (literówka) może doprowadzić do uszkodzenia zestawu.
Krok 1. Klikamy prawym przyciskiem myszy na nazwę modułu VHDL (zgodnie z poniższym zrzutem ekranu) i wybieramy opcję New source.
Tworzenie pliku ucf.
Krok 2. Jako rodzaj dodawanego modułu wybieramy Implementation Constraints File. Plik można nazwać dowolnie np. przypisanie_pinow. Upewniamy się koniecznie, że zaznaczone jest pole Add to project i klikamy Next.
Tworzenie własnego pliku ucf.
Krok 3. Program wyświetli podsumowanie ustawień, które może wyglądać następująco:
Za pomocą powyższych linii przypisujemy naszym sygnałom fizyczne piny układu. Informacje te są dość proste i najłatwiej zrozumieć ich "sens" analizując powyższy plik czytając wyjaśnienia:
NET oznacza połączenie,
LOC oznacza lokalizację, czyli numer pinu z dokumentacji układu.
IOSTANDARD oznacza standard napięciowy danego pinu (tutaj 3.3V),
PERIOD oznacza częstotliwość. Jej definiowanie ma sens oczywiście w przypadku sygnału zegarowego.
DRIVE oznacza wydajność prądową w mA dla danego pinu.
SLEW oznacza parametr, który można opisać jako zdolność do szybkiej zmiany stanu logicznego z 1 na 0 lub odwrotnie. W naszym przypadku szybkość nie stanowi priorytetu dlatego jest ustawiona na SLOW.
Wzrost szybkości jest okupiony większą emisją zakłóceń. Jest to złożone zjawisko. Więcej informacji na ten temat znaleźć można w Internecie pod hasłem Slew Rate.
Opis pinów można znaleźć na schemacie naszego zestawu uruchomieniowego. Na poniższych zrzutach ekranu widać wykorzystane przez nas piny:
Pin z sygnałem zegarowym.
Piny diod świecących.
Na koniec zapisujemy plik i przechodzimy do "wgrania programu"!
Wgrywanie konfiguracji na zestaw ElbertV2
Przed załadowaniem naszej aplikacji na płytkę ElvertV2 należy wykonać jeszcze jedną czynność. W panelu hierarchii projektu (po lewej stronie) na górze zaznaczamy plik VHDL. Poniżej wyświetli się lista możliwych procesów do wykonania na tym projekcie. Klikamy prawym przyciskiem myszy na Generate Programming File i wybieramy Process Properties.
Generowanie pliku bin.
W nowym oknie zaznaczamy pole Create Binary Configuration File, które znajdziemy na zakładce General Options. Następnie zatwierdzamy zmiany przyciskiem OK i idziemy dalej.
Zmiana ustawień projektu.
Teraz możemy już wygenerować plik zawierający konfigurację naszej aplikacji. Klikamy prawym przyciskiem myszy na Generate Programming File i wybieramy Run. Gdy wszystko pójdzie dobrze, to po pewnym czasie (do 5 minut) pojawią się zielone "ptaszki" przy polach:
Synthesize - XST,
Implement Design,
Generate Programming File.
Poprawne zakończenie procesu.
Do wgrania konfiguracji na FPGA potrzebny jest program ElbertV2Config do pobrania ze strony producenta lub bezpośrednio z tego miejsca.Pobranego pliku nie trzeba instalować, wystarczy go uruchomić. Program powinien wyglądać następująco:
Program służący do konfiguracji FPGA na płytce ElbertV2.
W miejscu Select Port wybieramy numer portu COM odpowiadającego zestawowi ElbertV2. Numer ten można odczytać w Menadżer Urządzeń. Klikamy Open File i z katalogu naszego projektu wybieramy plik z rozszerzeniem bin (np. mryganie_led.bin). Klikamy przycisk Program.
Jeśli wszystko przebiegło poprawnie, to diody powinny migać!
Warto w tym miejscu wyjaśnić, na czym polega "wgrywanie konfiguracji". W rzeczywistości podczas tego procesu programujemy pamięć FLASH znajdującą się na płytce ElbertV2. Konfiguracja podczas podłączenia zasilania układu jest wgrywana do FPGA za każdym razem.
W odróżnieniu od mikrokontrolerów układy FPGA przechowują konfiguracje do ustania zasilania. Nie ma tutaj procesu podawania rozkazu, jego dekodowania i wykonywania. Konfiguracja FPGA określa jego wewnętrzną strukturę "ad hoc" (łac. na dany moment).
Ćwiczenie 4.1
W ramach zadania domowego zajmij się przeanalizowaniem dzisiejszego, testowego programu. Spróbuj zmienić częstotliwość, z którą migają diody! W komentarzach czekamy na zdjęcia/filmy, dajcie znać, że wszystko działa poprawnie!
Podsumowanie
Od teraz każdy powinien już mieć skonfigurowane i gotowe do pracy środowisko. W kolejnych częściach kursu nie będziemy poświęcać już miejsca na opisywanie procesu tworzenia projektu i wgrywania konfiguracji do układu. W razie problemów zawsze będzie można wrócić do tego artykułu i podejrzeć jak było to robione za pierwszym razem. Warto również powoli oswajać się z pojęciami typu enity, port oraz architecture.
W następnych artykułach zajmiemy się wykorzystaniem informacji z kursu techniki cyfrowej (zaczniemy od bramek logicznych). Stopniowo będziemy również poznawać poszczególne elementy składni języka VHDL.
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...