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 \timescalealbo 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 #5to 5 nanosekund. Przy`timescale 1us / 1ns #5to 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