Kurs FPGA – #4 – pierwszy projekt, przykład VHDL

Kurs FPGA – #4 – pierwszy projekt, przykład VHDL

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.

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:

Klikamy Finish.

Zestaw elementów do kursu

Gwarancja pomocy na forum Błyskawiczna wysyłka

Zestaw uruchomieniowy Elbert v2 - Spartan 3A z wszystkimi niezbędnymi peryferiami do wykonania ćwiczeń z kursu FPGA!

Kup w Botland.com.pl

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.

Tworzenie modułu VHDL w projekcie.

Krok 7. Teraz pokaże się podsumowanie kreatora naszego modułu. Przykładowa zawartość:

Klikamy Finish. Naszym oczom ukarze się nowy projekt:

Projekt z utworzonym modułem VHDL.

Krok 8. Domyślny kod programu powinien wyglądać następująco:

Omówienie wygenerowanego kodu

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.

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.

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.

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:

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.

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.

W pierwszym projekcie wstawmy tam następujący fragment kodu (krótkie wyjaśnienia zawarte są w komentarzach):

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.

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:

  1. Słowa kluczowe VHDL zapisuje się małymi literami np.: "entity".
  2. Własne definicje (np. stałych wartości) zapisuje się wielkimi literami.
  3. 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.
  4. 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.

W tym miejscu wstawiamy poniższy kod (krótkie wyjaśnienia znajdują się w komentarzu):

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.

Pierwsza linia, to deklaracja stałej - będzie to maksymalna wartość licznika, którego wyjście będzie zmieniało stan diod świecących.

Kolejną instrukcją jest deklaracja sygnału lokalnego o nazwie licznik. Zmienna ta jest typu bez znaku i ma długość 25 bitów.

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.

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.

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.

Na końcu przekazujemy wartości sygnału lokalnego na piny zewnętrzne. Odbywa się to tutaj:

Na ten moment zawartość pliku (modułu) VHDL powinna wyglądać następująco:

Przygotowanie pliku konfiguracyjnego .ucf

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.

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.

kurs_fpga_3_35

dodawanie gotowego pliku ucf.

Krok 2. Jeśli wszystko poszło dobrze, to pojawi się poniższe okno:

dodawanie gotowego pliku .ucf 2.

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:

Jeśli wszystko się zgadza, to klikamy Finish i idziemy dalej.

Krok 4. Otwieramy plik ucf (jeśli nie otworzył się samoczynnie). Będzie on pusty. Wstawiamy do niego następujące komendy:

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.

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.

Pin z sygnałem zegarowym.

Piny diod świecących.

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 naszego FPGA na płytce ElbertV2

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.

Nawigacja kursu

Autor kursu: Adam Bemski
Redakcja: Damian Szymański
Testy, ilustracje: Piotr Adamczyk

O autorze: Adam Bemski

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.

fpga, ISE, kurs, kursFPGA

Komentarze

Komentarze do tego wpisu są dostępne na forum: