Menu

Timescale i opóźnienia w Verilogu: sterowanie czasem symulacji

Jak dyrektywa `timescale ustala jednostkę #delay, jakie reguły obowiązują przy łączeniu różnych jednostek w wielu plikach i jak opóźnienia współgrają z logiką taktowaną zegarem.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Co oznacza "czas" w Verilogu

Verilog nie ma wbudowanego pojęcia sekund. Symulator przesuwa "jednostki czasu", czyli dowolne całkowite tyknięcia, a twój kod używa #N, żeby odczekać N z nich. To dyrektywa `timescale przekłada te tyknięcia na rzeczywisty czas.

`timescale 1ns / 1ps

module test;
    initial begin
        $display("t = %0t", $time);   // 0
        #5;
        $display("t = %0t", $time);   // 5 ns
        #1.5;
        $display("t = %0t", $time);   // 6500 ps (or 6.5 ns)
        $finish;
    end
endmodule

Dwa parametry:

  • Jednostka (pierwsza liczba): co oznacza #1. 1ns mówi, że jedno tyknięcie to jedna nanosekunda.
  • Precyzja (druga liczba): jak dokładnie symulacja śledzi czas w obrębie tej jednostki. 1ps mówi, że ułamkowe opóźnienia są zaokrąglane do pikosekund.

Precyzja nie może być grubsza niż jednostka (1ps / 1ns jest niedozwolone). Typowe wybory:

  • 1ns / 1ps: standard de facto. Wszystko w nanosekundach, precyzja poniżej nanosekundy dla dowolnych opóźnień bramek.
  • 1ps / 1ps: gdy modelujesz ekstremalnie szybkie układy albo gdy każde opóźnienie jest krótsze niż nanosekunda.
  • 1us / 1ns: dla wolnych symulacji w skali systemów wbudowanych (czasy bajtów UART, wolne protokoły).

Gdzie to umieścić

`timescale to dyrektywa kompilatora, a nie konstrukcja na poziomie modułu. Umieszcza się ją na samym początku pliku, przed jakimkolwiek module:

`timescale 1ns / 1ps

module foo(...);
    // ...
endmodule

Jej zasięg to "od tego miejsca w kolejności kompilacji aż do następnego `timescale albo końca kompilacji". Ma to subtelną konsekwencję: plik bez jawnego `timescale dziedziczy to, co zadeklarował wcześniej skompilowany plik, a to zależy od kolejności, w jakiej kompilator czyta pliki. Unikaj niespodzianek: umieść `timescale na początku każdego pliku.

Edytor w przeglądarce w tej dokumentacji ustawia za ciebie domyślny timescale (zwykle 1ns / 1ps), dlatego przykłady z wcześniejszych stron działały bez jego deklarowania. W prawdziwym projekcie warto pisać go jawnie.

#delay w praktyce

Po `timescale 1ns / 1ps:

#5            // wait 5 ns
#100          // wait 100 ns
#1.5          // wait 1.5 ns (precision allows it)
#0.001        // wait 1 ps (just barely above precision)
#0            // zero-delay; useful for ordering events at the same time

Wewnątrz bloku initial lub always #N wstrzymuje przebieg proceduralny na N jednostek czasu. Symulator wstrzymuje ten blok (inne współbieżne bloki działają dalej) i wznawia go po upływie opóźnienia.

Opóźnienie możesz postawić przed przypisaniem:

#10 a = 1;        // wait 10 ns, then assign
data <= #2 new_value;   // schedule the non-blocking assignment 2 ns from now

Pierwsza forma to podstawa testbenchy. Druga (opóźnione przypisanie nieblokujące) służy w symulacji na poziomie bramek do modelowania opóźnienia propagacji.

Generowanie zegara

Klasyczny wzorzec:

`timescale 1ns / 1ps

module test;
    reg clk = 0;
    always #5 clk = ~clk;
    // ...
endmodule

Przy timescale 1ns / 1ps #5 to 5 ns. Zegar przełącza się co 5 ns, co daje okres 10 ns, czyli zegar 100 MHz. Żeby zmienić częstotliwość, zmień opóźnienie:

Pół okresuOkresCzęstotliwość
#12 ns500 MHz
#2.55 ns200 MHz
#510 ns100 MHz
#1020 ns50 MHz
#2550 ns20 MHz
#50100 ns10 MHz

Jeśli chcesz ułamkowego półokresu (#2.5), precyzja musi go obsługiwać. Precyzja 1ps radzi sobie ze wszystkim aż do 0,001 ns, więc każda rozsądna częstotliwość jest w porządku.

Mieszanie timescale w wielu plikach

Prawdziwy projekt składa się z wielu plików, a każdy może deklarować inny timescale. Symulator interpretuje opóźnienia w danym pliku według jego własnego timescale. Jeśli module_a.v ma `timescale 1ns / 1ps i używa #5, to oznacza 5 ns. Jeśli module_b.v ma `timescale 1us / 1ns i używa #5, to oznacza 5 us.

Zwykle tego nie widać, bo symulator i tak prezentuje jedną globalną oś czasu, ale oznacza to, że to samo #N w dwóch plikach może znaczyć coś zupełnie innego. Rozwiązanie: wybierz jeden timescale (standardem branżowym jest 1ns / 1ps) i umieść go na początku każdego pliku. Nie mieszaj.

Opóźnienia nie są syntezowalne

Kluczowa sprawa: #delay istnieje tylko na potrzeby symulacji. Narzędzie do syntezy czyta:

// In synthesizable RTL - WRONG
always @(posedge clk) begin
    out <= #2 in;
end

…i albo ignoruje #2 (większość narzędzi), albo odrzuca tę konstrukcję (surowsze lintery). Timing prawdziwego sprzętu wyznaczają zegar i opóźnienie propagacji bramek, a obu tych rzeczy nie widać w kodzie źródłowym.

Zasada: używaj # tylko w testbenchach. Syntezowalny RTL nie zawiera opóźnień #. Jeśli chcesz opóźnienia w syntezowalnym kodzie, tak naprawdę potrzebujesz licznika, który odlicza w dół w takt zegara. Tak "czeka" prawdziwy sprzęt.

$time a $realtime

Dwa sposoby odczytania bieżącego czasu symulacji:

  • $time zwraca 64-bitową liczbę całkowitą wyrażoną w jednostce bieżącego timescale.
  • $realtime zwraca wartość real wyrażoną w jednostce bieżącego timescale, ale z pełną precyzją.

Do logowania w testbenchu $time prawie zawsze wystarcza. Po $realtime sięgaj tylko wtedy, gdy w wydrukach potrzebujesz precyzji poniżej jednego tyknięcia.

Praktyczne wskazówki

  • Zawsze deklaruj `timescale na początku każdego pliku. 1ns / 1ps to bezpieczna wartość domyślna.
  • Używaj #delay tylko w testbenchach. Traktuj jego brak w syntezowalnym kodzie jak twardą regułę.
  • Dopasuj okresy zegara do docelowej częstotliwości. Jeśli symulujesz projekt 50 MHz, użyj okresu zegara 20 ns, bo niedopasowane okresy mogą maskować błędy zależne od timingu.
  • Do pobudzeń liczonych w cyklach używaj @(posedge clk) zamiast #. To rozwiązanie jest odporne na zmiany okresu zegara.

Co dalej

To już wszystkie strony tych samouczków Verilog. Od podstaw języka (wire a reg, moduły, operatory), przez bloki proceduralne i sterowanie przepływem, po projektowanie synchroniczne i maszyny stanów, aż do narzędzi testbenchowych, które dowodzą, że wszystko działa. Czas coś zbudować: edytor online obok tej dokumentacji to ten sam symulator, którego używaliśmy cały czas, gotowy na każdy moduł, który naszkicujesz.

Najczęściej zadawane pytania

Co oznacza `timescale 1ns / 1ps w Verilogu?

Mówi symulatorowi: 'jedna jednostka czasu w tym pliku to jedna nanosekunda, a czas jest śledzony z dokładnością do pikosekundy.' Po tej dyrektywie #5 czeka 5 ns, #1.5 czeka 1,5 ns (w zaokrągleniu do pikosekund), a $time jest podawany w nanosekundach. Pierwsza liczba to jednostka, druga to precyzja.

Czy każdy plik Verilog potrzebuje `timescale?

Dobra praktyka mówi: tak. Zasięg dyrektywy kończy się na następnym `timescale albo na końcu kompilacji, więc pliki bez niej dziedziczą to, co zadeklarował wcześniej skompilowany plik. Przez to timing staje się niedeterministyczny między kolejnymi buildami. Umieść `timescale 1ns / 1ps na początku każdego pliku źródłowego (to najpopularniejsza konwencja), a nic cię nie zaskoczy.

Co oznacza #5 w Verilogu?

#5 przesuwa czas symulacji o 5 jednostek czasu. Jednostkę określa aktywna dyrektywa `timescale. Przy `timescale 1ns / 1ps #5 to 5 nanosekund. Przy `timescale 1us / 1ns #5 to 5 mikrosekund. Liczba może być ułamkowa: #1.5 działa, jeśli precyzja jest drobniejsza niż jednostka.

Czy #delay jest syntezowalne w Verilogu?

Nie. #delay wpływa tylko na symulację, a narzędzie do syntezy je ignoruje albo odrzuca. Timing prawdziwego sprzętu wynika z sygnału zegara i opóźnienia propagacji bramek, a nie z instrukcji #. Używaj # swobodnie w testbenchach, ale nigdy nie pisz go w syntezowalnym RTL.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

Zacznij