Programowalne układy logiczne kod kursu: ETD008270 Układy kombinacyjne, przypisania, blokujące i nieblokujące cz.2 W4 23.03.2018 mgr inż. Maciej Rudek
UKŁADY KOMBINACYJNE
Układy kombinacyjne Układ kombinacyjny jest to układ dla którego zmiana na wejściu (którymkolwiek) powoduje natychmiastową zmianę na wyjściu. Uściślając wyjście układu kombinacyjnego jest zależne tylko od bieżących stanów na wejściu. Istnieją trzy sposoby opisu układów kombinacyjnych: Przypisania ciągłe Wykorzystanie instrukcji procesu always Za pomocą funkcji i zdań.
Przykład: always module muxn #(parametr N=1) ( input [N-1:0] A,B, output reg [N-1:0] Y, input S); always @( * ) always @(A,B,S) if (S==1) Y=B; else Y=A; endmodule Instrukcja zdarzenia rozpoczyna się od znaku @ - inaczej mówiąc jest to lista wrażliwościowa. Oznacza to, że zmiana któregokolwiek z sygnałów spowoduje wykonanie instrukcji. Czyli aktualizacja wyjścia nastąpi podczas zmiany jednego z wyjść w liście wrażliwościowej. A co w momencie gdy zapomnimy o wpisaniu do listy wrażliwościowej jednego z istotniejszych sygnałów?
Przykład: instrukcja warunkowa module muxn #(parametr N=1) ( input [N-1:0] A,B, output reg [N-1:0] Y, input S); always @(A,B,S) if (S==1) Y=B; else Y=A; endmodule output [N-1:0] Y, assign Y = (S)? B : A; Ogólna postać wyrażenia warunkowego ma postać: if (wyrażenie_x) if (wyrażenie_z) u = u+n; else u = 0; if (S==1) Y=B; else Y=A; assign Y = (S)? B : A;
Rejestry zatrzaskowe module XXX ( output reg f, input a, b, c); always @(*) if (a == 1) f = b & c; endmodule Czy układ ten będzie układem kombinacyjnym? W momencie gdy wejście a dla stanu niskiego nie jest w żaden sposób zdefiniowane wówczas wyjście f nie będzie ulegać zmianom. A wartość wyjścia f będzie zależeć od poprzedniej konfiguracji. Jest to rejestr zatrzaskowy (latch) Układ kombinacyjny jest to układ dla którego zmiana na wejściu (którymkolwiek) powoduje natychmiastową zmianę na wyjściu.
Rejestr zatrzaskowy układ kombinacyjny Dodając instrukcję else: module XXX ( output reg f, input a, b, c); always @(*) if (a == 1) f = b & c; else f = 0; endmodule Wykorzystując blok begin end: module XXX ( output reg f, input a, b, c); always @(*) begin f = 0; if (a == 1) f = b & c; end endmodule W momencie zamiany linijek przypisania wartości wyjścia f =0 z instrukcją if, nastąpi opisanie układu którego stan wyjścia będzie posiadać permanentny stan 0. Wynika to z wykonywania instrukcji po kolei, w kolejności zapisania. W obecnym stanie, wartość f uzyskuje tylko domyślny stan.
Instrukcja case module sm( output reg y, input a, b, c); wire = w={a, b, c}; //1 always @(*) begin y = 1 b1; //2 if (w==3 d0 w==3 d5 w==3 d6) y=1 b0; //3 end endmodule Jak to działa? a b c x 0 0 0 0 1 0 1 0 1 1 0 0 1 stworzenie wektora w z wejść a,b,c 2 ustawieni domyślnej wartości stanu na 1 3 wyznaczenie przypadków w których wyjście y posiada stan 0 na wyjściu.
Instrukcja case module cas ( output reg y, input a, b, c); always @(*) case ({a,b,c}) 3 b000: y = 1 b0; 3 b001: y = 1 b1; 3 b010: y = 1 b1; 3 b011: y = 1 b1; 3 b100: y = 1 b1; 3 b101: y = 1 b0; 3 b110: y = 1 b0; 3 b111: y = 1 b1; endcase endmodule module cas ( output reg y, input a, b, c); always @(*) case ({a,b,c}) 3 b000: y = 1 b0; 3 b101: y = 1 b0; 3 b110: y = 1 b0; default: y = 1 b1; endcase endmodule a b c x 0 0 0 0 1 0 1 0 1 1 0 0 Wykonywanie instrukcji odbywa się w kolejności w której zostały zapisane w opisie. Klauzula default pozwala uprościć zapis.
Instrukcja case module cas ( output reg y, input a, b, c); always @(*) case ({a,b,c}) 3 b000: y = 1 b0; 3 b101: y = 1 b0; 3 b110: y = 1 b0; default: y = 1 b1; endcase endmodule a b c x 0 0 0 0 1 0 1 0 1 1 0 0 Istotną informacją jest to, że w liście wyrażeń instrukcji case można przypisać więcej niż jedno wyrażenie. Wówczas kod z prawej strony można zapisać następująco: module cas ( output reg y, input a, b, c); always @(*) case ({a,b,c}) 3 d0, 3 d5, 3 d6: y = 1 b0; default: y = 1 b1; endcase endmodule
Wartości nieistotne - case module cas ( output reg y, input a, b, c); always @(*) case ({a,b,c}) 3 b001: y = 1 b1; 3 b010: y = 1 b1; 3 b011: y = 1 b1; 3 b100: y = 1 b1; 3 b110: y = 1 b0; 3 b111: y = 1 b1; default: f = 1 bx; endcase endmodule Dla wartości wejściowych a,b,c : 1, 2, 3, 4 i 7, wyjście przyjmie stan wysoki 1, a dla wartości 6 stan niski 0 ; Natomiast dla pozostałych stan nieustalony x Co dzięki temu zyskujemy? Powoduje to zoptymalizowanie układu kombinacyjnego przez narzędzia syntezy co wiąże się z minimalizacją funkcji logicznej oraz wykorzystaniem mniejszej ilości zasobów
Wartości nieistotne case (wewnątrz) Wartości nieistotne mogą występować również wewnątrz wyrażenia case. Wówczas należy skorzystać z instrukcji casex lub casez. module dek; reg [7:0]; always @(*) begin r = 8 bx1x0x1x0; casex (r) 8 b001100xx: instrukcja _1; 8 b1100xx00: instrukcja_2; 8 b00xx0011: instrukcja _3; 8 bxx001100: instrukcja _4; endcase end endmodule Jak to działa? Dla zmiennej 8-bitowej r posiadającej wartość: x1x0x1x0, zostanie wykonana instrukcja_2. A czy inststrukcja_4 też? NIE. Instrukcja_4 nie zostanie wykonana ze względu na priorytet kolejności występujący w opisie instrukcji case.
Wartości nieistotne case (wewnątrz) module cas ( output reg y, input a, b); always @(*) case ({a,b}) 2 b0?: y = 1 b1; 2 b10: y = 1 b0; 2 b11: y = 1 b1; endcase endmodule Jak to działa? Zastosowanie symbolu? w specyfikacji wyrażenia spowoduje że dla wartości wejścia a = 0 wyjście układu będzie w stanie wysokim, niezależnie od stanu na wejściu b. Notacja z wykorzystaniem symbolu? powoduje zwiększenie przejrzystości oraz zaoszczędzić wielkość kodu UWAGA: dla symulatora wartość x przyjmuje jedną z 4-ch wartości, a dla fizycznego obwodu będzie to 0 lub 1
Atrybuty
Atrybuty Są to dodatkowe informacje dla narzędzi syntezy, które mogą zmienić sposób działania np.: zoptymalizować przetwarzaną funkcję logiczną. Wstawiane sa tak jak komentarz w konstrucji: (* <atrybut> *) Mogą być stosowane w formie przedrostka do deklaracji modułu, instrukcji lub połączenia portu, albo jako przyrostek związany z operatorami lub nazwami funkcji podczas jej wywołania. (* INIT= 1 *) reg Q; Sum a + (* ripple_adder *) b;
Atrybuty W Verilogu NIE definiuje się konkretnych nazw atrybutów, lecz sposób w jaki zostaną wykorzystane. Nazwy, wartości oraz znaczenie atrybutów zależą od narzędzi syntezy które są stosowane. chip_pin direct_enable full_case keep maxfan max_depth multstyle noprune parallel_case preserve ram_init_file ramstyle romstyle read_comments_as_hdl syn_encoding translate_off translate_on useioff verilog_input_version
Atrybuty: full_case i parallel_case full_case i parallel_case są atrybutami stosowanymi do instrukcji case. Instrukcja case w domyślny sposób jest typem pełnym (full), tzn. że przyjmuje wszystkie możliwe kombinacje stanów w sposób jawny lub za pomocą klauzuli default. Bez definiowania wszystkich stanów w instrukcji case, narzędzia syntezy zaimplementują w strukturę rejestr zatrzaskowy. Jest to niepożądana opcja w układach kombinacyjnych!
full_case W momencie nie wymienienia wszystkich stanów w instrukcji case a chcąc zachować jego działanie jako układ kombinacyjny można posłużyć się atrybutem full_case. Jego użycie spowoduje traktowanie instrukcji case (w każdym przypadku) jako typu pełnego. A nie opisane stany wejść będą traktowane jako nieistotne. Tym samym rejestry zatrzaskowe nie zostaną zaimplementowane w strukturę układu.
parallel_case Funkcja case będzie typu równoległego (parallel) w momencie gdy w danym czasie nie więcej niż jedno z wyrażeń związanych z case_element odpowiada co do wartości wyrażeniu znajdującemu się po słowie kluczowym case. Np. w momencie gdy wyrażenie jest zmienną, wówczas narzędzia syntezy nie mogą statycznie określić czy dana instrukcja case jest typu równoległego. Tak jak poprzednio, instrukcje case są wykonywane w kolejności występowania w opisie, wówczas może zostać zaimplementowany dekoder priorytetowy. Wiąże się to z nadmiarową rozbudową struktury co często jest nie wykorzystywane. Jeśli wiadomo, że w danym czasie tylko jedno wyrażenie case_element będzie równe wyrażeniu po słowie kluczowym to można wykorzystać do tego celu atrybut parallel_case.
Elementy trójstanowe Kolejnym układem kombinacyjnym sa elementy trójstanowe, które mogą przyjmować jedną z 3-ch wartości 0, 1, Z. W Języku Verilog można zamodelować taki układ strukturalnie lub behewioralnie. module 3state (input in,en, output reg out); always @(*) if(en) out = in; else out = 1'bz; // assign out = EN? In : {1 bz}; endmodule
Funkcje i zadania Funkcje i zadania = funkcje i procedury Funkcja odpowiedzialna jest za przyjmowanie i zwracanie wartość niezbędnych do wykonania z operacji obliczenia np.: dodawania wielu zmiennych, algorytmu Zellera, Euklidesa, etc. Procedura (zadanie) - jest to fragment kodu który został wydzielony z większej całości i funkcjonuje osobno w celu realizowania konkretnego zadania / funkcji np.: wyświetla znak, zapala diodę, rysuje, wypisuje dane na wyświetlaczu.
Funkcje i zadania Zalety: Zwiększa czytelność kodu behewioralnego Podział opisu struktury na mniejsze bloki Ukrywanie niektórych danych dla dla reszty częsci projektu Umożliwia ułatwienie utrzymania kodu i jego modyfikację Zdefiniowane raz funkcje/zadania mogą być wykorzystywane wielokrotnie w projekcie
Porównanie cech funkcji i zadań Kategoria Funkcje Zadania Wywoływanie (aktywowanie) Wywołanie innych zadań i funkcji Funkcję wywołuje wyrażenie z wartością którą może zwrócić lub zostać użyta w tym wyrażeniu w celu obliczeń. Funkcja można wywołać w przez przypisanie ciągłe lub procedularnym. Z wnętrza funkcji możliwe jest wywołanie innej funkcji ale NIE można wywoływać zadania. Możliwe jest wywołanie przez funkcję tej samej funkcji lub rekurencyjnie. Osobna instrukcja procedularna wywołuje zadanie i nie może zostać wywołana z przypisania ciągłego Zadanie ma mozliwość wywołania innego zadania lub nawet funkcji. Wejścia i wyjścia Funkcja posiada co najmniej jedno wejście, jednak nie posiada portu dwukierunkowego (I/O). Z funkcji jednak można zwrócić wartość mimo braku typowego wyjścia. Zadanie nie musi mieć argumentów ale może posiadać ich wiele oraz dowolnego typu.
Zastosowanie funkcji - przykład Funkcja realizuje zadanie szyfrowania, przestawiają kolejność 8-bitowego słowa wejściowego, Wykorzystując operację selekcji bitowej oraz bitowej różnicy symetrycznej dokonywane jest przestawienie kolejności bitów. Wywołanie funkcji oraz przypisanie do odpowiednio starszych i młodszych części słowa.
Porównanie cech funkcji i zadań Kategoria Funkcje Zadania Instrukcje zdarzeni @, opóźnienia # i instrukcja wait W funkcji nie można wykorzystywać instrukcji zdarzenia W zadaniach można używać instrukcji zdarzenie. Mogą one być aktywowane współbieżnie, przez wywołanie ich z współbieżnych bloków: always/initial. Pamięć (zasoby) Zwracanie wartości Pamięć przydzielana jest statycznie dla wejść funkcji oraz zadeklarowanych wewnątrz zmiennych. Pamięć może być dynamiczna przez wykonanie ich kopii wartości i deklaracje automatyczną Do wyrażenia z którego została wywołana, funkcja zwraca tylko jedną wartość. Wartość zwracana jest do identyfikatora nazwy funkcji. Pamięć przydzielona jest statyczna dla wejść, wyjść oraz wewnętrznych zmiennych. Współbieżne wywołanie dzieli zasoby. Dzięki deklaracji automatycznej można przydzielić pamięć dynamicznie. Wartość nie jest zwracana przez zadanie, ale może zapisać ją do swoich portów dwukierunkowych lub portu wyjściowego. Jest ona kopiowana po zakończeniu wykonywania zadania.
Funkcja realizuje zadanie szyfrowania, przestawiają kolejność 8-bitowego słowa wejściowego, Parametr key został przekazany nie za pośrednictwem listy portów a jako sygnał zewnętrzny. Zadanie musi zostać wywołane z wnętrze procesu (instrukcji always) a NIE przez przypisanie ciągłe jak to ma miejsce w funkcji.
Pętle Pętle jak w wielu językach programowania mogą zostać wykorzystane w języku Verilog w symulacjach lub syntezy. W tym ostatnim jednak mogą zostać zastosowane w ściśle określonych warunkach i zależnie od stosowanych narzędzi syntezy. forevr repeat while for W języku Verilog mamy do dyspozycji cztery różne pętle
Pętla: forever Między pętlą forever a blokiem alweys występuje pewna analogia obie działają podobnie, wykonując daną instrukcję w nieskończoności. Jednak wykorzystujac pętlę można ją przerwać przez wykonanie instrukcji disable lub $finish. Pętla ta nie jest syntezowalna dla narzędzi Xilinx Synthesis Technology (XST)
Pętla: repeat Pętla repeat wykonuję się określoną ilość razy w zależności od wartości wyrażenia, które jest oblizane na samym początku jej wykonania. Nie można zmienić jej z wnętrza pętli. Pętla ta jest syntezowalna dla narzędzi XST dla stałej wartości wyrażenia.
Pętla: while Pętle while wykonywane są warunkowo, w momencie w którym wyrażenie jest prawdziwe, a jej wartość musi być modyfikowana z wnętrza pętli. While a nie można wykorzystywać do oczekiwania na zmianę sygnału, który jest generowany na zewnątrz. Pętla ta jest syntezowalna dla narzędzi XST.
Przykład poprawnego zastosowania Przedstawiona konstrukcja jest układem kombinacyjnym, obliczającym ilość zer w słowie wejściowym (16-bitowym) W jaki sposób uzyskać układ do obliczania jedynek wykorzystując ten kod?
Pętla for Działanie pętli for jest podobne do tej znanej z języka C i C++. Jednak w celu umożliwienia syntezowania kodu przez narzędzia XST należy spełnić następujące warunki: Z góry znana ilość iteracji pętli przez określone statycznie granicznych wartości, Wykorzystanie operatorów <, <=, >, >=. Obliczanie kroku jest wykonywane przez następującą formułę: i = i + 1 lub i = i 1 (wartość = wartość ± krok)
Przykład dla pętli for Przedstawiona konstrukcja jest układem kombinacyjnym, obliczającym ilość zer w słowie wejściowym (16-bitowym)
Instrukcja disable Zakończenie warunków kontynuacji jest możliwe do wykonania także w języku Verilog za pomocą instrukcji disable. Jednak jej funkcja jest bardziej ogólna niż dla typowych języków programowania. Po napotkaniu instrukcji disable, sterowanie przechodzi do następnej instrukcji poza blokiem begin end Dla narzędzi syntezy XST. Instrukcja ta jest NIE syntezowalna
Instrukcja generate Podobnie jak w tablicy instancji tak i dzięki instrukcji generate możliwe jest modelowanie wielu instancji danego obiektu np.: modułów, Podstawowe elementy logiczne Bloki procedularne initial i always Przypisanie ciągłe Deklaracje zmiennych i sieci Definicje zadań i funkcji Redefinicja parametrów
Przypomnienie: Tablice instancji module buf( input [1:8] in, oe, output [1:8] out); bufif0 ( out[1], in[1],oe[1] ), ( out[2], in[2],oe[2] ), ( out[3], in[3],oe[3] ), ( out[4], in[4],oe[4] ), ( out[5], in[5],oe[5] ), ( out[6], in[6],oe[6] ), ( out[7], in[7],oe[7] ), ( out[8], in[8],oe[8] ); endmodule 8-bitowy moduł bufora trójstanowego module suma( input [1:8] in, oe, output [1:8] out); bufif0 b[1:8] (out, in, oe); endmodule
Wykorzystanie bloku generate Blok generate endgenerate określa jak dane bloki będą powtarzane. Aby pętla for mogła wygenerować selekcję bitów należy wykorzystać wewnętrzną zmienną typu genvar. W rezultacie stworzone zostają 4 kopie instrukcji bufora
Wykorzystanie bloku generate
Wykorzystanie bloku generate Wykorzystanie bloku always spowoduje utworzenie czterech bloków always
Przypomnienie: Moduły module sumator_2( input [1:0] P,Q, output [1:0] S, input CI, output CO); suma modul_1(.a(p[0]),.b(q[0]),.y(s[0]),.cout(cy0),.cin(ci)); suma modul_2(.a(p[1]),.b(q[1]),.y(s[1]),.cout(co),.cin(cy0)); module suma( input A,B,Cin, output Y, Cout); [ ] endmodule
Wykorzystanie bloku generate Blok: suma W poniższym przykładzie przedstawiono połączenie jednobitowego sumatora sum1 z wykorzystaniem bloku generate. Wewnątrz funkcji for zostało wykorzystane generowanie instancji modułu sum1 oraz jego odpowiednie połączenie dla poszczególnych stopni.
Przykłady realizacji enkodera Jet to jedne z podstawowych rozwiązań enkodera 8- bitowego priorytowego. Zastosowano w nim instrukcję warunkową oraz proces always. W tej konstrukcji (if else) nie ma potrzeby stosowania bloku begin end ze względu zastosowanie pojedynczej instrukcji.
Przykłady realizacji enkodera 2 Dlaczego odwrócono kolejność sprawdzania zapalonego bitu? Ze względu na wykonywanie kolejności sprawdzania. Gdyby w kodzie zastosowano sprawdzanie od najstarszego do najmłodszego, wówczas w momencie gdy bit młodszy posiadał stan wysoki, sygnał na wyjściu byłby nieprawidłowy i reprezentował by stan z tego bitu a nie starszego. W tym przykładzie zastosowania konstrukcję enkodera bez wykorzystania klauzuli else, w tym celu należało bezwzględnie zastosować blok begin end (dlaczego?).
Przykłady realizacji enkodera 3 Zasadniczo nie ma co tutaj opisywać. Wykorzystano instrukcję casex śledząc pojawienie się tylko jedynki w miejscach istotnych.
Przykłady realizacji enkodera 4 Jest to związane z niesyntezowalnością instrukcji disable, którą należałoby zastosować przy testowaniu od bitu najstarszego, słowa wejściowego. Przykład enkodera stworzony z wykorzystaniem funkcji. Wartym zaznaczenia jest fakt, iż w pętli for testowanie słowa wejściowego zaczyna się on bitu najmłodszego. Dlaczego?
Przykłady realizacji enkodera 5 Ten kod został sparametryzowany umożliwiając tym samym przystosowanie go do dowolnej liczby bitów. W zaznaczonej linijce uzyskano przypisanie wstępnej wartości dla wyjścia układu stosując operację sklejania i replikacji.
Przykłady realizacji enkodera 6 Przykład enkodera z wykorzystaniem przypisania ciągłego
Dziękuję za uwagę :)