Menu

Verilog timescale и delays: управление временем симуляции

Как директива timescale задаёт единицу #delay`, правила комбинирования разных единиц через файлы и как задержки взаимодействуют с тактируемой логикой.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Что значит "время" в Verilog

У Verilog нет встроенного понятия секунд. Симулятор продвигает "единицы времени" - произвольные целочисленные тики - и твой код использует #N, чтобы ждать N таких. Директива `timescale - то, что отображает эти тики в реальное время.

`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 (или 6.5 ns)
        $finish;
    end
endmodule

Два параметра:

  • Единица (первое число): что значит #1. 1ns говорит, что один тик - одна наносекунда.
  • Precision (второе число): насколько тонко симуляция отслеживает время внутри этой единицы. 1ps говорит, что дробные задержки округляются до пикосекунд.

Precision не может быть грубее единицы (1ps / 1ns нелегально). Частые выборы:

  • 1ns / 1ps - de facto стандарт. Всё в наносекундах, sub-нс precision для gate delays.
  • 1ps / 1ps - при моделировании очень быстрых схем или когда все задержки sub-нс.
  • 1us / 1ns - для медленных embedded-симуляций (UART byte times, медленные протоколы).

Куда её ставить

`timescale - это директива компилятора, не module-конструкция. Идёт в самом верху файла, до любого module:

`timescale 1ns / 1ps

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

Её scope - "с этого момента дальше в порядке компиляции, до следующего `timescale или конца компиляции". Это имеет тонкое следствие: файл без явного `timescale наследует то, что объявил предыдущий скомпилированный файл, что зависит от порядка чтения файлов компилятором. Избегай сюрприза: ставь `timescale наверху каждого файла.

Редактор в браузере в этих документах задаёт дефолтный timescale за тебя (обычно 1ns / 1ps), поэтому примеры в более ранних документах работали без объявления. В реальном проекте нужно быть явным.

#delay на практике

После `timescale 1ns / 1ps:

#5            // ждать 5 нс
#100          // ждать 100 нс
#1.5          // ждать 1.5 нс (precision позволяет)
#0.001        // ждать 1 пс (чуть выше precision)
#0            // нулевая задержка; полезно для упорядочивания событий в одно время

Внутри блока initial или always #N блокирует процедурный поток на N единиц времени. Симулятор приостанавливает этот блок (другие параллельные продолжают идти) и возобновляет после задержки.

Можно префиксить присваивание задержкой:

#10 a = 1;        // ждать 10 нс, потом присвоить
data <= #2 new_value;   // запланировать non-blocking присваивание через 2 нс

Первая форма - staple testbench. Вторая (delayed non-blocking) используется в gate-level-симуляции для моделирования propagation delay.

Генерация clock

Классический паттерн:

`timescale 1ns / 1ps

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

С timescale 1ns / 1ps #5 - это 5 нс. Clock переключается каждые 5 нс, давая период 10 нс - clock 100 МГц. Чтобы поменять частоту, меняй задержку:

Полу-периодПериодЧастота
#12 нс500 МГц
#2.55 нс200 МГц
#510 нс100 МГц
#1020 нс50 МГц
#2550 нс20 МГц
#50100 нс10 МГц

Если хочешь дробный полу-период (#2.5), precision должен поддерживать - 1ps precision держит всё до 0.001 нс, так что любая разумная частота - норм.

Смешивание timescale между файлами

В реальном проекте много файлов, и они могут объявлять разные timescale. Симулятор использует свой timescale каждого файла для интерпретации задержек в нём. Если module_a.v говорит `timescale 1ns / 1ps и использует #5, это 5 нс. Если module_b.v говорит `timescale 1us / 1ns и использует #5, это 5 мкс.

Это в основном невидимо, потому что симулятор представляет глобальную ось времени независимо - но значит, что одно и то же #N в двух файлах может значить дико разные вещи. Фикс: выбери один timescale (промышленный стандарт - 1ns / 1ps) и ставь его наверху каждого файла. Не смешивай.

Задержки не синтезируются

Критический момент: #delay существует только для симуляции. Синтезатор читает:

// В синтезируемом RTL - НЕПРАВИЛЬНО
always @(posedge clk) begin
    out <= #2 in;
end

…и либо игнорирует #2 (большинство тулов), либо отвергает конструкцию (более строгие линтеры). Тайминг реального железа определяется clock и propagation delay вентилей - оба невидимы для исходника.

Правило: используй # только в testbench. У синтезируемого RTL нет #-задержек. Если хочется задержки в синтезируемом коде, на самом деле тебе нужен счётчик, тикающий вниз по clock - так реальное железо "ждёт".

$time vs $realtime

Два способа прочитать текущее симуляционное время:

  • $time возвращает 64-битный integer в единицах текущего timescale.
  • $realtime возвращает real в единицах текущего timescale, но с полной precision.

Для логирования testbench $time почти всегда хватает. К $realtime тянись только когда нужна sub-tick precision в принтах.

Практичные подсказки

  • Всегда объявляй `timescale наверху каждого файла. 1ns / 1ps - безопасный дефолт.
  • Используй #delay только в testbench. Считай его отсутствие в синтезируемом коде статическим правилом.
  • Сопоставляй периоды clock целевой частоте. Если симулируешь дизайн на 50 МГц, используй период clock 20 нс - несовпадающие периоды могут скрыть timing-sensitive баги.
  • Для cycle-counted стимулов используй @(posedge clk) вместо #. Это устойчиво к изменениям периода clock.

Что дальше

Ты теперь увидел каждый документ в этих туториалах по Verilog. От основ языка (wire vs reg, modules, операторы), через процедурные блоки и control flow, в синхронный design и state machines, и наконец инструменты testbench, доказывающие, что всё работает. Время что-нибудь построить - playground рядом с этими документами использует тот же симулятор, что мы использовали всё это время, готовый к любому module, который ты набросаешь.

Часто задаваемые вопросы

Что значит `timescale 1ns / 1ps в Verilog?

Это говорит симулятору: 'одна единица времени в этом файле - это одна наносекунда, а времена отслеживаются с точностью до пикосекунды'. После этой директивы #5 ждёт 5 нс, #1.5 ждёт 1.5 нс (округлённое до пикосекунд), и $time репортится в наносекундах. Первое число - единица; второе - precision.

Нужен ли `timescale в каждом Verilog-файле?

Best practice: да. Scope директивы кончается на следующей \timescaleили в конце компиляции, так что файлы без неё наследуют то, что объявил предыдущий скомпилированный файл. Это делает timing недетерминированным между сборками. Ставь`timescale 1ns / 1ps` наверху каждого исходника - это самое распространённое соглашение - и сюрпризов не будет.

Что значит #5 в Verilog?

#5 продвигает симуляционное время на 5 единиц времени. Единица приходит из активной директивы \timescale. С `timescale 1ns / 1ps, #5- это 5 наносекунд. С`timescale 1us / 1ns, #5- это 5 микросекунд. Число может быть дробным -#1.5` работает, если precision тоньше единицы.

Синтезируется ли #delay в Verilog?

Нет. #delay влияет только на симуляцию - синтезатор игнорирует или отвергает его. Тайминг реального железа приходит от clock-сигнала и propagation delay вентилей, не от операторов #. Используй # свободно в testbench; никогда не пиши его в синтезируемом RTL.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ