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.