Układy FPGA - projektowanie krok po kroku

| Technika

Układy FPGA (Field Programmable Gate Arrays) stale zyskują na popularności. Dawniej, ze względu na łatwość rekonfiguracji, używano ich przede wszystkim do budowy prototypów w projektach na bazie układów ASIC. Obecnie, głównie dzięki malejącej cenie i rosnącej mocy obliczeniowej, układy FPGA są coraz częściej częścią urządzeń elektronicznych dostępnych w sprzedaży, m.in. tych do komunikacji w sieciach 3G/4G, odbiorników HDTV oraz sprzętu medycznego, na przykład aparatów USG 3D.

Układy FPGA - projektowanie krok po kroku

Pierwszym krokiem w procesie przygotowywania projektu urządzenia opartego na układzie FPGA jest opracowanie zarysu architektury w pseudokodzie. Pisząc go, można się skupić na realizacji konkretnej funkcjonalności urządzenia, nie przywiązując wagi do poprawności składni. W rezultacie pseudokod jest zrozumiały dla jego autora, ale niekoniecznie użyteczny w dalszych etapach konfiguracji FPGA. Dlatego następnie taka funkcjonalna charakterystyka układu programowalnego "jest tłumaczona" na język wysokiego poziomu.

Jest to zwykle jeden z języków opisu sprzętu HDL (Hardware Description Language). Dwa najpopularniejsze to: VHDL (Very high speed integrated circuit Hardware Description Language) oraz Verilog. Składnia tego pierwszego przypomina tę Pascala (albo Ady), natomiast Veriloga - języka C. VHDL jest zaliczany do grupy języków silnie typowanych, natomiast Verilog do tych słabo typowanych. Ponadto Verilog, w przeciwieństwie do VHDL (case-insensitive), rozpoznaje wielkie oraz małe litery (case sensitive). Składnia VHDL w porównaniu do Veriloga jest bardziej złożona. Dlatego analiza kodu napisanego w tym drugim jest prostsza, zwłaszcza w przypadku rozbudowanych projektów. Nauka programowania w Verilogu sprawia również mniej kłopotów początkującym.

Przykład kodu VHDL

Listing 1. Składnia opisu w języku VHDL

Na listingu 1 przedstawiamy ogólną składnię opisu układu logicznego w języku VHDL, natomiast na listingu 2 przykładowy opis trzywejściowej bramki NAND. Składa się on z dwóch bloków: deklaracji jednostki projektowej (entity) oraz deklaracji i opisu architektury (architecture). Ta pierwsza charakteryzuje wejścia oraz wyjścia układu, w tym ich typy. Architektura opisuje z kolei funkcje i (lub) strukturę układu.

Deklaracja jednostki projektowej rozpoczyna się od słowa kluczowego entity, po którym podać należy jej nazwę. Ta ostatnia poprzedza kolejne słowa kluczowe: is oraz port. Po tym drugim następuje deklaracja sygnałów wejściowych (in) oraz wyjściowych (out) jednostki. Oprócz jednokierunkowych portów wejściowych i wyjściowych można skorzystać z portów: inout, czyli wejścia/wyjścia, które pozwala na zapis i odczyt informacji oraz buffer. Ten ostatni to port wyjściowy, ale z odczytem stanu.

Listing 2. Opis trzywejściowej bramki NAND w języku VHDL

Opis jednostki projektowej kończy słowo kluczowe end, jej nazwa oraz średnik. Deklaracja architektury rozpoczyna się od słowa kluczowego architecture, po którym trzeba podać jej nazwę. Dalej występuje słowo kluczowe of oraz nazwa jednostki projektowej, której dana architektura dotyczy. Następne są słowa kluczowe is oraz begin, po których podaje się ciąg współbieżnych instrukcji będących ciałem architektury. Deklarację kończy słowo kluczowe end, nazwa architektury oraz średnik.

Zaletą języków opisu sprzętu HDL jest to, że skomplikowane układy cyfrowe za ich pomocą przedstawia się, pisząc zwięzły kod. Ponadto jeden projekt można później zaimplementować w różnych układach, chociaż projektant nie musi znać dokładnie ich architektury.

Kod wielokrotnego użytku

Listing 3. Przykład użycia parametru typu generic

Wielokrotne wykorzystywanie gotowego kodu albo chociaż jego fragmentów skraca czas przygotowywania projektu urządzenia i pozwala programistom skupić się na rozwijaniu jego unikalnej funkcjonalności. Dalej przedstawiamy kilka wskazówek, w jaki sposób taki uniwersalny kod przygotowywać w języku VHDL. Stosując się do tych zaleceń, małym kosztem i bez nadmiernego rozbudowywania kodu można go napisać w sposób "elastyczny", nawet jeżeli nie od razu wiadomo, czy i do czego dany program przyda się w przyszłości.

Pierwszy przykład przedstawiono na listingu 3. W kodzie tym zadeklarowano parametr width typu generic, przy użyciu którego opisywane są porty jednostki projektowej. Parametry generic są także użyteczne w implementowaniu kilku wariantów danej jednostki projektowej. W tym celu należy na przykład zadeklarować w ten sposób zmienną typu boolean jak na listingu 4, a następnie wykorzystać ją w instrukcji warunkowej if.

Listing 4. Parametr generic w instrukcji warunkowej

Operacje przetwarzania sygnałów często realizowane są na blokach danych, dlatego warto rozważyć przedstawienie takiego zbioru w formie tablicy. Na przykład chcąc utworzyć kilka kopii jednostki projektowej, z których każda wykonywać będzie daną operację na jednym z wielu równoległych strumieni danych, można wykorzystać fragment kodu z listingu 5. Liczbę wektorów danych w zbiorze łatwo wówczas zmienić przez modyfikację odpowiedniej zmiennej. Warto także zwrócić uwagę na sposób użycia w tym kodzie atrybutu‚ range.

Ten ostatni wykorzystano do opisu danych wejściowych również we fragmencie przedstawionym na listingu 6, który opisuje operację sumowania. Trzeba przy tym pamiętać, że typ elementu (data_vec) tablicy (collection_type) musi być statyczny globalnie. Oznacza to niestety, że nie da się wykorzystać deklaracji generic do parametryzacji rozmiarów data_vec i collection_type jednocześnie - zamiast tego użyć należy stałych. Innym rozwiązaniem na obejście tego ograniczenia jest wykorzystanie tablicy dwuwymiarowej jak na listingu 7. Wówczas zarówno collection_size, jak i data_width można zadeklarować w sekcji generic. Niestety wpływa to na czytelność kodu - elastyczność programu uzyskiwana jest zatem kosztem jego prostoty.

Tworzenie kodu wielokrotnego użytku ułatwia również umieszczenie deklaracji komponentu dla bloku w pakiecie (jak na listingu 8), do którego należy się w dowolnym miejscu w programie odwołać instrukcją: use work.my_package.all.

Kompilacja, ułatwienia programowe

Listing 5. Przykład wykorzystania tablic

Kolejnym etapem jest kompilacja kodu w języku HDL do postaci niskopoziomowego opisu urządzenia (netlisty). Następnie ten ostatni mapowany jest na strukturę konkretnego układu programowalnego. Na tej podstawie generowany jest strumień bitów używany do skonfigurowania układu FPGA.

Aplikacja warunkuje wymagania czasowe oraz jakościowe dla projektu urządzenia budowanego na bazie FPGA. W produkcji to jakość powinna być najważniejsza. W razie gdy w strukturze tej implementowany jest "tylko" prototyp, zwykle to jednak czas jest priorytetem. Konstruktorzy, którzy chcą jak najszybciej wdrożyć swoje pomysły, godzą się wtedy na nieco gorszą jakość. Dalej przedstawiamy rozwiązania stosowane w narzędziach do projektowania układów FPGA, które przyspieszają ten proces.

Zazwyczaj nawet ponad połowę czasu zajmuje etap implementacji projektu w strukturze układu programowalnego (place and route). Jego przeciąganie się jest szczególnie niepożądane, jeżeli trzeba przetestować niewielką oraz niekrytyczną dla działania układu zmianę w projekcie lub wymagana jest wyłącznie wstępna wersja prototypu urządzania. Jedna z technik usprawniająca takie działania polega na porównywaniu netlist i wykrywaniu dzielących je różnic, a później wprowadzaniu tylko ich. Można się również zdecydować na place and route w trybie szybkim, ale mniej dokładnym.

W trybie szybkim da się również skompilować kod w języku HDL. Jeżeli jednak w trakcie syntezy wystąpi jakiś błąd, proces ten najczęściej zostanie przerwany, a jego wznowienie nastąpi dopiero po rozwiązaniu problemu. To może skutkować dużym opóźnieniem, jeżeli błędów o różnym charakterze będzie bardzo dużo. Lepszym rozwiązaniem jest zbiorcze wykrywanie wszystkich błędów, bez przerywania procesu syntezy. W niektórych narzędziach jest to dopuszczalne po wcześniejszym zaznaczeniu odpowiedniej opcji (na przykład Continue synthesis on error).

Debugowanie

Listing 6. Opis operacji sumowania

Między przedstawionymi krokami przeprowadza się symulacje, na przykład sprawdzające poprawność opisu w kodzie w języku HDL. Można w ten sposób wychwycić i na bieżąco skorygować różne błędy. Nie zaleca się jednak, aby był to jedyny sposób debugowania. Niektórych błędów podczas symulacji po prostu nie da się wychwycić, dlatego po zaprogramowaniu przeprowadza się jeszcze testy w układzie (in-circuit).

Kluczową kwestią jest wybór właściwej metody debugowania. Idealnie byłoby, gdyby nie była ona niczym ograniczona i pozwalała na zbadanie poprawności różnych projektów, umożliwiała sprawdzenie działania układu FPGA w odniesieniu do systemu, którego jest on częścią oraz gwarantowała dokładne ustalenie przyczyn ewentualnych problemów. Do wyboru są dwie metody debugowania w układzie. W pierwszej korzysta się z wbudowanego analizatora stanów logicznych (integrated logic analyzer, ILA). Alternatywą jest użycie zewnętrznego sprzętu testującego, na przykład oscyloskopu mixed signal lub analizatora stanów logicznych.

Analizatory wbudowane vs. zewnętrzne

Listing 7. Przykład wykorzystania tablicy dwuwymiarowej

Przykładami wbudowanych analizatorów stanów logicznych są zintegrowane z układami FPGA firm Altera oraz Xilinx odpowiednio: SignalTap II oraz ChipScope ILA. Przykładowo w wypadku tego pierwszego wyjścia z użytkowej części układu programowalnego są połączone z wejściami bloku analizatora. Ten ostatni można z zewnątrz skonfigurować za pośrednictwem interfejsu JTAG. Służy on również do przesyłu danych zarejestrowanych przez ILA do komputera, na którym trzeba zainstalować oprogramowanie do ich analizy.

Listing 8. Deklaracje wspólne dla wielu projektów warto zapisywać w pakietach

Dalej przedstawiamy zalety oraz ograniczenia debugowania układów FPGA za pośrednictwem zintegrowanych analizatorów stanów logicznych i tych zewnętrznych. ILA korzystają z wewnętrznych zasobów FPGA (bloków logicznych, pamięci), które zostają w związku z tym wyłączone z normalnego użytkowania. Z tego powodu wbudowane analizatory logiczne są najczęściej częścią tych układów programowalnych, które są przystosowane do takiego dodatkowego obciążenia.

Kolejnym ograniczeniem jest to, że chociaż zintegrowane analizatory stanów logicznych dostarczają informacji o działaniu testowanego układu FPGA, nie ma możliwości odniesienia tych danych do innych, na poziomie płyty lub całego urządzenia. Tymczasem skorelowanie sygnału z i spoza układu FPGA jest często konieczne, aby dało się określić przyczyny jego błędnego działania.

Kompromisy

Następnym dylematem, przed jakim staje konstruktor, wybierając metodę debugowania swojego projektu, jest kompromis między kosztami a funkcjonalnością. ILA są oczywiście rozwiązaniem tańszym niż specjalistyczny sprzęt. Jak się można jednak spodziewać, możliwości tych pierwszych są mniejsze. Przykładowo ponieważ ILA korzystają z pamięci układu FPGA, parametry próbkowania takie jak na przykład długość rekord akwizycji (acquisition memory depths) są ograniczone. To z kolei utrudnia rozwiązanie problemów, których przyczyny i skutki dzieli duży przedział czasowy.

Zewnętrzny sprzęt testujący w ogóle nie korzysta z zasobów badanego układu FPGA. Do jego podłączenia wymagane mogą być jednak specjalne złącza, które trzeba zamontować na PCB. Zaletą tego "ograniczenia" jest możliwość skorelowania sygnałów z układu programowalnego z sygnałami z innych części systemu. Rekompensatą za większą cenę są lepsze parametry akwizycji.

Monika Jaworowska