Menu

Timescale e ritardi in Verilog: controllare il tempo di simulazione

Come la direttiva `timescale fissa l'unità di #delay, le regole per combinare unità diverse tra file e come i ritardi interagiscono con la logica sincronizzata dal clock.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Cosa significa "tempo" in Verilog

Verilog non ha un concetto integrato di secondi. Il simulatore fa avanzare delle "unità di tempo", cioè tick interi arbitrari, e il tuo codice usa #N per attenderne N. La direttiva `timescale è ciò che collega quei tick al tempo reale.

`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

Due parametri:

  • Unità (primo numero): cosa significa #1. 1ns dice che un tick è un nanosecondo.
  • Precisione (secondo numero): con quale finezza la simulazione traccia il tempo all'interno di quell'unità. 1ps dice che i ritardi frazionari vengono arrotondati ai picosecondi.

La precisione non può essere più grossolana dell'unità (1ps / 1ns non è valido). Le scelte più comuni:

  • 1ns / 1ps: lo standard di fatto. Tutto in nanosecondi, con precisione sotto il nanosecondo per eventuali ritardi di porta.
  • 1ps / 1ps: quando modelli circuiti estremamente veloci o quando ogni ritardo è inferiore al nanosecondo.
  • 1us / 1ns: per simulazioni lente in stile embedded (tempi di byte UART, protocolli lenti).

Dove metterlo

`timescale è una direttiva del compilatore, non un costrutto a livello di modulo. Va proprio in cima al file, prima di qualsiasi module:

`timescale 1ns / 1ps

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

Il suo ambito va "da questo punto in avanti nell'ordine di compilazione, fino al successivo `timescale o alla fine della compilazione". Questo ha una conseguenza sottile: un file senza un `timescale esplicito eredita quello dichiarato dal file compilato prima, e quindi dipende dall'ordine in cui il compilatore legge i file. Evita la sorpresa: metti `timescale in cima a ogni file.

L'editor nel browser di queste guide imposta un timescale predefinito al posto tuo (di solito 1ns / 1ps), ed è per questo che gli esempi delle guide precedenti funzionavano senza dichiararne uno. In un progetto reale conviene essere espliciti.

#delay in pratica

Dopo `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

Dentro un blocco initial o always, #N blocca il flusso procedurale per N unità di tempo. Il simulatore mette in pausa questo blocco (gli altri blocchi concorrenti continuano a girare) e riprende dopo il ritardo.

Puoi anteporre un ritardo a un'assegnazione:

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

La prima forma è un classico dei testbench. La seconda (non-blocking con ritardo) si usa nella simulazione a livello di porte per modellare il ritardo di propagazione.

Generare un clock

Il pattern classico:

`timescale 1ns / 1ps

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

Con timescale 1ns / 1ps, #5 vale 5 ns. Il clock commuta ogni 5 ns, con un periodo di 10 ns: un clock a 100 MHz. Per cambiare la frequenza, cambia il ritardo:

SemiperiodoPeriodoFrequenza
#12 ns500 MHz
#2.55 ns200 MHz
#510 ns100 MHz
#1020 ns50 MHz
#2550 ns20 MHz
#50100 ns10 MHz

Se vuoi un semiperiodo frazionario (#2.5), la precisione deve supportarlo: con precisione 1ps si scende fino a 0.001 ns, quindi qualsiasi frequenza ragionevole va bene.

Mescolare timescale tra file

Un progetto reale ha molti file, e possono dichiarare timescale diversi. Il simulatore usa il timescale di ciascun file per interpretare i ritardi di quel file. Se module_a.v dichiara `timescale 1ns / 1ps e usa #5, sono 5 ns. Se module_b.v dichiara `timescale 1us / 1ns e usa #5, sono 5 us.

Di solito non te ne accorgi, perché il simulatore presenta comunque un unico asse temporale globale, ma significa che lo stesso #N in due file può voler dire cose molto diverse. La soluzione: scegli un solo timescale (lo standard del settore è 1ns / 1ps) e mettilo in cima a ogni file. Non mescolarli.

I ritardi non sono sintetizzabili

Punto fondamentale: #delay esiste solo per la simulazione. Lo strumento di sintesi legge:

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

…e ignora il #2 (la maggior parte degli strumenti) oppure rifiuta il costrutto (i linter più severi). Il timing dell'hardware reale è determinato dal clock e dal ritardo di propagazione delle porte, entrambi invisibili nel codice sorgente.

La regola: usa # solo nei testbench. L'RTL sintetizzabile non ha ritardi #. Se ti ritrovi a volere un ritardo nel codice sintetizzabile, in realtà ti serve un contatore che scende a ogni ciclo di clock: è così che l'hardware reale "aspetta".

$time vs $realtime

Due modi per leggere il tempo di simulazione corrente:

  • $time restituisce un intero a 64 bit espresso nell'unità del timescale corrente.
  • $realtime restituisce un real espresso nell'unità del timescale corrente, ma con precisione completa.

Per i log dei testbench, $time basta quasi sempre. Ricorri a $realtime solo quando ti serve una precisione inferiore al tick nelle istruzioni di stampa.

Consigli pratici

  • Dichiara sempre `timescale in cima a ogni file. 1ns / 1ps è il valore predefinito sicuro.
  • Usa #delay solo nei testbench. Considera la sua assenza nel codice sintetizzabile come una regola fissa.
  • Adatta il periodo di clock alla frequenza di destinazione. Se simuli un progetto a 50 MHz, usa un periodo di clock di 20 ns: periodi sbagliati possono nascondere bug sensibili al timing.
  • Per gli stimoli contati a cicli, usa @(posedge clk) invece di #. Resiste ai cambi del periodo di clock.

Cosa viene dopo

Hai visto tutte le guide di questi tutorial Verilog. Dai fondamenti del linguaggio (wire vs reg, moduli, operatori), passando per i blocchi procedurali e il controllo del flusso, fino alla progettazione sincrona e alle macchine a stati, e infine agli strumenti di testbench che dimostrano che tutto funziona. Ora tocca a te costruire qualcosa: il playground accanto a queste guide è lo stesso simulatore che abbiamo usato finora, pronto per qualsiasi modulo tu voglia abbozzare.

Domande frequenti

Cosa significa `timescale 1ns / 1ps in Verilog?

Dice al simulatore: 'un'unità di tempo in questo file è un nanosecondo, e i tempi vengono tracciati con precisione al picosecondo.' Dopo quella direttiva, #5 attende 5 ns, #1.5 attende 1.5 ns (arrotondati ai picosecondi) e $time viene riportato in nanosecondi. Il primo numero è l'unità, il secondo è la precisione.

Serve `timescale in ogni file Verilog?

Come buona pratica, sì. L'ambito della direttiva termina al successivo \timescaleo alla fine della compilazione, quindi i file che non ne hanno uno ereditano quello dichiarato dal file compilato prima. Così il timing diventa non deterministico tra una build e l'altra. Metti`timescale 1ns / 1ps` in cima a ogni file sorgente (è la convenzione più diffusa) e non avrai mai sorprese.

Cosa significa #5 in Verilog?

#5 fa avanzare il tempo di simulazione di 5 unità di tempo. L'unità viene dalla direttiva \timescaleattiva. Con`timescale 1ns / 1ps, #5vale 5 nanosecondi. Con`timescale 1us / 1ns, #5vale 5 microsecondi. Il numero può essere frazionario:#1.5` funziona se la precisione è più fine dell'unità.

#delay è sintetizzabile in Verilog?

No. #delay agisce solo sulla simulazione: lo strumento di sintesi lo ignora o lo rifiuta. Il timing dell'hardware reale dipende dal segnale di clock e dal ritardo di propagazione delle porte, non dalle istruzioni #. Usa # liberamente nei testbench, ma non scriverlo mai nell'RTL sintetizzabile.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA