TC Moduł 13
Projektowanie układów cyfrowych cz. 2.
W niniejszym wykładzie omawiamy projektowanie układów cyfrowych z zastosowaniem języków opisu sprzętu. W poprzednim wykładzie omówiliśmy zasady specyfikacji języka AHDL. Omówienie bardziej rozbudowanych języków HDL np. VHDL lub Verilog jest niemożliwe ze względu na ograniczony zakres wykładu. I dlatego główna część niniejszego wykładu poświęcona będzie specyfikacjom w języku AHDL. Jednak w celu pokazania większych związków techniki cyfrowej z współczesną informatyką, w dalszej (nieobowiązkowej) części wykładu omawiamy specyfikacje tego samego układu w bardziej zaawansowanych i abstrakcyjnych językach VHDL i Verilog. |
W niniejszym module przedstawimy proces projektowania układu cyfrowego z użyciem języków opisu sprzętu HDL. Zaprojektujemy układ konwertera liczby binarnej na dwucyfrową liczbę BCD. Działanie algorytmu zostało szczegółowo omówione w module 11, a teraz skupimy się na wyspecyfikowaniu tego układu w wersji behawioralnej.
Działanie układu opisane jest siecią działań i składa się z trzech kroków: wprowadzenia danych, przetwarzania i wypisania danych.
|
Konwerter zapiszemy w języku AHDL. Jak już wiemy, każdy projekt musi składać się z co najmniej dwóch sekcji: sekcji interfejsu oraz opisu działania. Tutaj mamy sekcję interfejsu, czyli w języku AHDL sekcję SUBDESIGN. Projekt nazywa się bin2bcd. Sygnałami wejściowymi są: ośmiobitowy wektor lb, za pomocą którego przekazujemy liczbę binarną do konwersji, skalar start – sygnalizujący rozpoczęcie przetwarzania oraz skalar zegar do synchronizacji automatu i przerzutników. Sygnałami wyjściowymi są: ośmiobitowy wektor ld na którym pojawi się wynik czyli liczba BCD oraz skalar koniec – sygnalizujący moment wystawienia przetworzonej liczby. Dodatkowo, w sekcji zmiennych VARIABLE, zadeklarowane są zmienne zbudowane z przerzutników typu D. W rejestrze lda i ldb przechowywane są, odpowiednio, starsza i młodsza cyfra liczby BCD, rejestr lb_r służy do zapamiętania wejściowej liczby binarnej, rejestr lk odlicza liczbę kroków, a wyjście koniec jest synchronizowane sygnałem zegarowym. |
Początek drugiej wymaganej sekcji – opisu logiki – otwiera słowo kluczowe BEGIN (a kończy END). Ponieważ opisywany układ jest układem synchronicznym, to do wszystkich wejść przerzutników clk należy doprowadzić sygnał zegar. Nawiasami okrągłymi zgrupowano wejścia tego samego typu, co umożliwiło skrócony zapis. Jeżeli na wejściu start pojawi się sygnał logiczny „1”, to nastąpi wpisanie liczby binarnej z wejścia lb do rejestru wewnętrznego lb_r oraz licznik kroków lk zostanie ustawiony na wartość 8 – tyle potrzebnych jest cykli zegara, aby otrzymać dwie 4-bitowe cyfry liczby BCD. W języku AHDL domyślnymi równaniami dla przerzutników są równania zerujące, stąd nie musimy inicjować rejestrów lda i ldb. |
Następnie w kolejnych krokach jest dokonywana konwersja na liczbę BCD. Jeżeli spełniony jest warunek ldb >= 5 to następuje zwiększenie zawartości rejestru ldb o +3 oraz przesunięcie zawartości rejestrów lda i ldb. Dla tego warunku najstarszy bit ldb[3] ma zawsze wartość „1”, dlatego wpisujemy bit „1” na najmłodszej pozycji rejestru lda. Można sprawdzić, że dla starszej cyfry lda, nigdy nie zachodzi warunek lda >= 5, stąd nie pojawia się on w kodzie AHDL. Jeżeli nie jest spełniony warunek ldb >= 5, to następuje przesunięcie bitowe rejestrów ldb i lda. W każdym cyklu zegara przesuwana jest zawartość rejestru lb_r oraz zmniejszany licznik kroków lk. Główna pętla algorytmu wykonywana jest dopóty, dopóki zawartość licznika kroków lk nie jest równa zero. |
Po ośmiu taktach zegara wyliczone cyfry z rejestrów lda i ldb zapisywane są w rejestrze wyjściowym ld, a na wyjściu koniec pojawia się „1”. Gdyby nie było podtrzymania zawartości rejestrów lda i ldb (lda[]=lda[]; ldb[]=ldb[];), to jak już było wcześniej wspomniane, kompilator wygenerowałby równania zerujące i stracilibyśmy wyliczoną liczbę. |
Ze względu na swoją złożoność urządzenia cyfrowe muszą być projektowane w sposób hierarchiczny. Polega to na wyodrębnieniu w projektowanym urządzeniu części składowych realizujących pewne jednostkowe funkcje w taki sposób, aby po połączeniu ich w całość były realizowane zadania projektowanego urządzenia. Następnie część tak wyodrębnionych podprojektów jest realizowana z wykorzystaniem gotowych makrofunkcji bibliotecznych, rdzeni projektowych – modułów IPCore lub modułów zrealizowanych wcześniej przez projektanta (system reuse). W rezultacie jedynie niektóre części składowe projektu muszą być projektowane przez użytkownika jako nowe, oddzielne moduły. Podział projektowanego urządzenia na części składowe umożliwia bardziej efektywną realizację całości, gdyż moduły składowe, z założenia prostsze w swej budowie, są łatwiejsze do zaprojektowania, optymalizacji i weryfikacji. Moduły z niższego poziomu hierarchii mogą być dalej dzielone na mniejsze części w zależności od potrzeb i koncepcji projektanta. Po stworzeniu wszystkich części składowych urządzenia i ich przetestowaniu użytkownik tworzy projekt poprzez połączenie tych części w całość. |
Hierarchia już zrealizowanego w systemie Quartus układu cyfrowego, pokazuje realizację schematu blokowego układu pokazanego na poprzedniej planszy. Układ operacyjny konwertera składa się z ośmiu modułów: czterech rejestrów R1, R2, R3, R4, licznika LK, sumatora S, komparatora K i multipleksera M. W module układu sterującego US opisano główny automat wraz z liniami sterującymi do modułu operacyjnego UO. |
Pokazana jest specyfikacja najmniejszym składowych w projekcie: modułów kombinacyjnych, sumatora, komparatora i multipleksera, oraz modułów synchronicznych, tutaj programowalny rejestr z funkcją ładowania load i zmniejszania zawartości dec. |
Na planszy pokazano przykładową realizację pozostałych rejestrów, z wykorzystaniem konstrukcji CASE lub IF..ELSE. Każdy z tych rejestrów ma nieco inną funkcję. Pokazuje to możliwości języka opisu sprzętu – jego elastyczność, analogicznie jak w językach programowania. |
Mając już składowe elementy, można opisać moduł operacyjny UO. Na początku należy poinformować kompilator z jakich modułów bibliotecznych będziemy korzystać, czyli należy podać deklaracje funkcji zapisane w zbiorach z rozszerzeniem inc – include file. Zawartość przykładowego pliku deklaracji pokazano na planszy dla modułu komparatora k.inc. W sekcji VARIABLE deklarujemy zmienne typu makrofunkcja, na przykład: zmienna komp jest typu k. |
W sekcji logiki układu operacyjnego następuje konkretyzacja czyli użycie zadeklarowanych wcześniej zmiennych typu makrofunkcja. Zmienną taką możemy porównać do struktury w językach programowania, gdzie do pól tej struktury dostajemy się poprzez odwołanie kropkowe. W językach HDL pola makrofunkcji nazywane są portami. Dzięki zastosowaniu łączenia sygnałów za pomocą funkcji konkatenacji, możliwy jest zwięzły i krótki zapis połączeń pomiędzy modułami składowymi. Na przykład, zapis rej1.(a[], s[]) = (lb[], s_[1..0]); mówi, że do portów a i s zmiennej rej1 przyłączone będą odpowiednio wektory lb i część wektora 's_. Należy pamiętać, że kompilator dba jedynie o to, aby wektory składowe lewej strony równania były tej samej sumarycznie długości co suma długości wektorów prawej strony. Projektant musi zwracać szczególną uwagę, na poprawne podłączanie sygnałów, aby zapewnić poprawną pracę układu. |
Moduł układu sterującego jest zrealizowany z użyciem automatu. W sekcji VARIABLE zadeklarowany jest ośmiostanowy automat bez wyspecyfikowania na jakich przerzutnikach ma być realizacja oraz bez podania kodów dla poszczególnych stanów. Kompilator sam zadba o odpowiednią realizację automatu w zależności od zadanych w systemie parametrów oraz od docelowej architektury układu programowalnego. W sekcji logiki zrealizowana jest tablica przejść-wyjść automatu z wykorzystaniem instrukcji CASE. |
Ostatni w hierarchii moduł łączy układ operacyjny i sterujący. Podobnie jak w realizacji behawioralnej, sygnał wyjściowy koniec jest sygnałem synchronicznym. W okienku Nawigatora Projektu widać nazwę układu, dla którego była przeprowadzona kompilacja EPF10K20RC240-3, co oznacza układ typu FPGA z rodziny Flex10K, o wielkości logicznej 20 (20 tysięcy przeliczeniowych bramek), w katalogowej obudowie RC z 240 wyprowadzeniami i szybkości „3”. |
Złożoność i abstrakcyjność zasad specyfikacji języka VHDL, znacznie różniącego się od stosowanych do tej pory prostych języków HDL dla układów programowalnych powoduje, że jego stosowanie w syntezie układów cyfrowych wymaga od projektanta większego wysiłku. Jednak zaletą jest to, że język ten jest językiem standardowym, a przez to kod HDL jest w większości przenaszalny na inne systemy cyfrowe. Cechą charakterystyczną języka VHDL jest jego ogólność, która wyraża się między innymi tym, że jego podstawowe elementy strukturalne ograniczają się przede wszystkim do obiektów danych, instrukcji i deklaracji, a różnorodność zasad specyfikacji znalazła odbicie w różnorodności instrukcji. W sposób ogólny przedstawiono specyfikację układu konwertera w języku VHDL. Na planszy pokazano deklaracje użytych bibliotek, które umożliwiają użycie obiektów i wykonywaniu operacji na tych obiektach. Poniżej zapisana jest sekcja interfejsu, tutaj nazywana deklaracją jednostki projektowej ENTITY. |
W bloku architektury ARCHITECTURE jest opisane działanie lub struktura realizowanego układu. Zawiera on część deklaracyjną, w tym przykładzie nie ma, oraz część instrukcyjną, po słowie kluczowym BEGIN. Słowo kluczowe PROCESS jest instrukcją współbieżną, zawierającą instrukcje sekwencyjne. Instrukcja ta jest wykonana, gdy zmieni się wartość sygnału wyspecyfikowanego w liście wrażliwości. Na przykład, instrukcja PROCESS(zegar) zostanie wykonana przy zmianie sygnału zegar. Poniżej jest deklaracja zmiennych lokalnych. Należy zwrócić uwagę, iż zmienne deklarowane są dla typu reprezentacji liczb, a nie dla typu kombinacyjnego lub rejestrowego. To czy zmienna będzie realizowała część kombinacyjną czy rejestrową zależy od jej użycia. |
Za pomocą zdefiniowanego w języku VHDL atrybutu EVENT dla sygnału zegar oraz sprawdzeniu stanu logicznego tego sygnału można wyszczególnić tzw. moment charakterystyczny sygnału. W tym przykładzie jest to zbocze narastające sygnału zegarowego czyli zmiana sygnału zegarowego z wartości „0” na „1”. W języku VHDL zmienne należy inicjować. |
Główna pętla algorytmu jest zrealizowana analogicznie jak w AHDL. Należy zwrócić uwagę, że instrukcja „&” służy do łączenia sygnałów (konkatenacji), a nie jest to operator typu AND. |
Zmienne rejestrowe w języku VHDL opisują zmianę sygnału, czyli zazwyczaj realizują funkcję przerzutnika typu zatrzask (latch). Z tego powodu, nie trzeba, jak to było w AHDL, podtrzymywać zawartości rejestrów lda i ldb. |
Analogicznie opisane są poszczególne moduły w realizacji strukturalnej. |
Pokazano niektóre przykładowe moduły. |
Obiekty definiowane przez użytkownika czyli komponenty COMPONENT (stałe, typy, podtypy, sygnały, zmienne, funkcje, procedury) umieszcza się w modułach zwanych pakietami PACKAGE. Aby skorzystać z takiego pakietu, należy go zadeklarować. Specjalna biblioteka WORK stanowi zbiór obiektów użytkownika i umieszcza się ją w katalogu bieżącym projektu. W jednostce architektury umieszczone są konkretyzacje zadeklarowanych komponentów (tutaj zadeklarowanych w pakiecie my_package). Przekazywanie sygnałów w komponentach jest poprzez nazwy portów. Możliwe jest też przekazywanie sygnałów przez pozycję w liście sygnałów. |
Moduł pakietu my_packege zawiera deklaracje komponentów. Jak można zauważyć, instrukcja COMPONENT jest przepisaną jednostką deklaracji ENTITY. |
Automat w układzie sterującym specyfikuje się przez zadeklarowanie typu i wypisaniu wartości, a następnie zadeklarowaniu sygnału o podanym typie. W języku VHDL sposób kodowania stanów automatu można określić na poziomie ustawień kompilatora. Na slajdzie mamy dwie instrukcje współbieżne PROCESS. Pierwsza opisuje przejścia automatu, druga opisuje wyjścia automatu. |
Na tej planszy można zobaczyć inna metodę deklaracji komponentów. Komponenty umieszczane są w jednostce architektury w części deklaracyjnej. |
Język Verilog powstał w latach 80. Jest językiem standardowym, coraz chętniej wykorzystywanym do opisu układów cyfrowych, ponieważ jego składnia jest oparta na elementach najbardziej popularnego języka programowania – języka C. Jest też językiem bardziej elastycznym w porównaniu z językiem VHDL. W przykładach pokazane będą elementy języka Verilog wykorzystane do specyfikacji konwertera bin2bcd. Interfejs układu cyfrowego opisuje moduł module z listą sygnałów wejścia, wyjścia i dwukierukowych. Poniżej występują deklaracje sygnałów wejścia i wyjścia. Na przykład, sygnały zegar i start są tego samego typu, a już sygnał lb jest innego typu, ponieważ dwa pierwsze sygnały są skalarami, a trzeci jest wektorem ośmiobitowym. Dalej deklarowane są zmienne typu reg, czyli zmienne mogące zapamiętać wartość sygnału. Należy zwrócić uwagę, że typ reg nie jest deklaracją typu przerzutnika, ale w układzie cyfrowym jest realizowany na przerzutnikach. Język Verilog rozróżnia wielkość liter. |
Odpowiednikiem instrukcji PROCESS, w języku Verilog jest instrukcja always. W nawiasie wymieniona jest lista wrażliwości. Atrybut posedge określa zbocze narastające sygnału. W języku Verilog zmienne należy inicjować. Instrukcja przypisania lda = 4'd0; określa 4-bitową liczbę w systemie dziesiętnym o wartości 0 wpisaną do zmiennej lda. |
Instrukcja łączenia wektorów jest realizowana za pomocą nawiasów {}. Znana z języka C instrukcja przesunięcia << realizuje w przykładzie lb_r = lb_r << 1 operację przesunięcia logicznego w lewo o jeden bit. |
Podobnie jak w języku VHDL, zmienne reagują na zmianę sygnału, czyli realizują funkcję zatrzasku latch. Stąd, nie ma potrzeby podtrzymywania wartości zmiennych. |
Każdy projekt po udanej kompilacji, czyli po usunięciu błędów składni języka, należy zweryfikować. Weryfikację poprawności realizacji dokonuje się na drodze symulacji: funkcjonalnej bądź czasowej. Symulacja czasowa rożni się od funkcjonalnej tym, że na przebiegach odpowiedzi sygnałów uwzględnia się model opóźnieniowy układu dla którego przeprowadzono kompilację. |
Przykładowe specyfikacje składowych modułów w realizacji strukturalnej konwertera bin2bcd. W module multipleksera użyto operatora trójargumentowego ?:, realizującego operację warunkową. W module komparatora pokazano użycie instrukcji always dla specyfikacji układu kombinacyjnego. |
Przykładowe specyfikacje wielofunkcyjnych rejestrów z wykorzystaniem instrukcji case oraz if...else. |
Jak w każdym języku wysokiego poziomu, jedynie podzbiór standardu języka Verilog jest syntetyzowany. W tym przykładzie, kompilator Quartus nie obsługuje dyrektywy include. Pliki wchodzące w skład projektu muszą być w tym samym katalogu. Aby połączyć moduły, należy zadeklarować sygnały typu wire. Typ wire nie jest elementem pamiętającym, reprezentuje fizyczne połączenia pomiędzy elementami, wartość jest ustalana na podstawie sygnałów zasilających. Konkretyzacje modułów są poprzez zadeklarowane typy modułów, a sygnały przekazywane przez nazwy portów. Na przykład, w instrukcji k komp(.a(mu_y), .y(k)); jest zadeklarowany moduł komp typu k i do portu a podawany jest sygnał mu_y, a do portu y – sygnał k. |
W module sterującym automat zdefiniowany jest za pomocą zmiennej typu reg, a poszczególne stany automatu zadeklarowane są parametrem parameter. |
Automat jest wyspecyfikowany dwoma konstrukcjami always. Jedna opisuje przejścia automatu, jest to część sekwencyjna. Druga opisuje wyjścia, czyli część kombinacyjną. |
Najwyższy w hierarchii moduł bin2bcd.v łączy układ operacyjny i układ sterujący. |
Przykładowy ekran pokazujący wynik symulacji konwersji liczby binarnej o wartości 99 na liczbę BCD. Każda cyfra BCD ma wartość równą 9 co daje się łatwo reprezentować za pomocą liczby szesnastkowej. |