Przed lekturą niniejszego artykułu koniecznie przeczytaj odcinek 22, ponieważ dokładnie wytłumaczono w nim sposób działania algorytmu. A skoro nie musimy ponownie wyjaśniać tego zagadnienia, od razu przystąpimy do omawiania implementacji algorytmu sposobem kombinacyjnym.
Zasadnicza różnica między implementacją sekwencyjną i kombinacyjną jest taka, że układ kombinacyjny nie ma elementów pamięciowych tzn. przerzutników. Nie potrzebuje także sygnału zegarowego ani resetującego. Składa się tylko z bramek logicznych, multiplekserów, sumatorów i innych elementów kombinacyjnych, a stan jego wyjść zależy wyłącznie od obecnego stanu wejść. Po zmianie stanu wejść stan wyjść ustala się natychmiast po upływie pewnego czasu, zwanego czasem propagacji (więcej na ten temat pisaliśmy w odcinku 11, poświęconym statycznej analizie czasowej i maksymalnej częstotliwości zegara).
Moduł DoubleDabble w wersji kombinacyjnej
Przejdźmy do analizy kodu, pokazanego na listingu 1. Wejścia, wyjścia i parametry działają tak samo jak w poprzednim odcinku, z tą różnicą, że usunięte zostały wszystkie wejścia/wyjścia sterujące maszyną stanów, wejście zegarowe i reset. Z nich wszystkich pozostało tylko wejście Binary_i, na które dostarczamy liczbę zapisaną w formacie binarnym – oraz wyjście BCD_o, z którego odczytywać będziemy wynik w formacie BCD.
// Plik double_dabble.v
`default_nettype none
module DoubleDabble #(
parameter INPUT_BITS = 16,
parameter OUTPUT_DIGITS = 5,
parameter OUTPUT_BITS = OUTPUT_DIGITS * 4
)(
input wire [ INPUT_BITS-1:0] Binary_i,
output reg [OUTPUT_BITS-1:0] BCD_o
);
// Iteratory pętli for
integer i; // 1
integer j; // 2
// Blok kombinacyjny
always @(*) begin // 3
BCD_o = 0; // 4
// Dla każdego bitu na wejściu
for(i=0; i<INPUT_BITS; i=i+1) begin // 5
// Dla każdej cyfry na wyjściu
for(j=0; j<OUTPUT_BITS; j=j+4) begin // 6
// Jeżeli cyfra jest >= 5
if(BCD_o[j+:4] >= 4’d5) begin // 7
// To dodaj 3 do tej cyfry
BCD_o[j+:4] = BCD_o[j+:4] + 4’d3; // 8
end
end
// Przesuń bity w rejestrze
// i dodaj kolejny bit z wejścia danych Binary_i
BCD_o = {BCD_o[OUTPUT_BITS-2:0], // 9
Binary_i[(INPUT_BITS-1)-i]};
end
end
endmodule
`default_nettype wire
Listing 1. Kod pliku double_dabble.v
Parametr INPUT_BITS określa, ile bitów ma mieć wejście liczby binarnej. OUTPUT_DIGITS definiuje, ile cyfr BCD ma być na wyjściu, a OUTPUT_BITS ustala, ile bitów ma mieć to wyjście. Jeżeli w instancji modułu wspomniany parametr nie zostanie zdefiniowany, to ustawi się automatycznie jako liczba cyfr pomnożona przez 4. Czasem okaże się, że nie ma potrzeby, by używać dokładnie takiej liczby bitów – wówczas parametr ten można ustawić ręcznie. Przykładem opisanej sytuacji jest wejście 10-bitowe, na którym można ustawić liczbę od 0 do 1023, czyli cztery cyfry. W kodzie BCD zajmują 16 bitów, lecz pierwsza cyfra jest w stanie przyjmować wartość jedynie 0 lub 1. Zatem nie ma przeszkód, by skrócić ją jedynie do jednego bitu – i w takiej sytuacji wyjście może mieć jedynie 13 bitów.
W naszym module będziemy stosować dwie zagnieżdżone pętle for. Pierwsza z nich przeznaczona jest do wykonywania operacji na każdym bicie na wejściu Binary_i, a druga – na każdej cyfrze na wyjściu BCD_o. W języku Verilog iteratory pętli musimy utworzyć, zanim rozpocznie się pętla for. Robimy to w liniach 1 i 2, w których tworzymy zmienne typu integer o nazwach i oraz j (uwaga na przyzwyczajenia z C i C++, w Verilogu zmienna całkowita to integer, a nie int).
Moduł składa się tylko z jednego kombinacyjnego bloku always (linia 3). Rozpoznajemy ten fakt po liście czułości bloku – zamiast @(posedge Clock, negedge Reset), tym razem widzimy @(*), co oznacza, że blok ma wykonać jakąś operację zawsze wtedy, kiedy zmieni się dowolna zmienna w nim odczytywana.
Przypominam, że w blokach kombinacyjnych stosujemy przypisania blokujące = zamiast nieblokujących <=. Ponadto, jeżeli jakaś zmienna jest modyfikowana w wielu miejscach, to syntezator weźmie pod uwagę to przypisanie, które zostaje wykonane jako ostatnie. Zasada działania wygląda podobnie, jak w standardowych językach programowania.
Jedyną zmienną, na której będziemy pracować, jest BCD_o, stanowiąca jednocześnie wyjście modułu. W linii 4 nadajemy tej zmiennej wartość 0 (aby przypisać jakąś wartość początkową), a następnie będziemy ją modyfikować na różne sposoby.
Kod algorytmu Double Dabble w wersji kombinacyjnej jest niezwykle zwarty. Składa się z zaledwie kilku linijek, przez co wygląda dość prosto, lecz może okazać się trudny do zrozumienia. Postaram się wytłumaczyć go w przystępny sposób.