Menu

For-циклы в Verilog: развёрнуты на compile time

Чем for-циклы Verilog отличаются от софтовых родственников - синтезатор разворачивает их в параллельное железо, а не выполняет итеративно в runtime.

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

Софтовый двойник

for-цикл Verilog - копия C:

for (i = 0; i < 8; i = i + 1) begin
    // тело
end

Те же три части: инициализатор, условие, инкремент. Тело перезапускается, пока условие истинно.

В testbench ведёт себя ровно как ожидаешь от софта. Симулятор шагает через каждую итерацию по очереди:

Четыре итерации, четыре строки вывода. Никаких сюрпризов.

Сюрприз приходит, когда вставляешь for-цикл в синтезируемый код.

Разворот

for-цикл в синтезируемом always-блоке не становится runtime-циклом в железе. Синтезатор разворачивает его на elaboration - расширяет цикл в N копий тела, где N - счётчик итераций:

Выглядит как цикл. В симуляции симулятор реально проходит восемь итераций. В синтезе цикл разворачивается в восемь параллельных проверок data[0] через data[7], всё происходит одновременно. Синтезатор видит:

count = 0;
if (data[0]) count = count + 1;
if (data[1]) count = count + 1;
if (data[2]) count = count + 1;
...
if (data[7]) count = count + 1;

…и превращает последовательность в дерево сумматоров. Runtime-поведение - "смотри на все 8 бит сразу и посчитай, сколько 1", в одном комбинационном проходе.

Следствие: for-цикл в синтезируемом Verilog не бесплатный. 64-итерационный цикл становится 64 копиями тела в железе. Если тело сложное, ты только что построил большой комбинационный блок. Используй циклы, когда N маленькое (горсточка или несколько десятков). Для больших счётов обычно хочется тактируемый счётчик и state machine.

Требуются константные границы

Синтезатор может развернуть цикл только если знает N на elaboration. Это значит, границы должны быть константами:

// Работает - граница константа
for (i = 0; i < 8; i = i + 1) ...

// Работает - граница parameter
for (i = 0; i < WIDTH; i = i + 1) ...

// Не синтезируется - граница зависит от runtime-сигнала
for (i = 0; i < dynamic_count; i = i + 1) ...

Последняя форма может работать в симуляции, но синтезатор её отвергнет. Если реально нужен runtime-счётный цикл, ты строишь его через тактируемый state machine и регистр-счётчик - у железа нет циклов с переменным числом проходов так, как у софта.

generate for vs процедурный for

Отдельная, но связанная конструкция - generate for, использует genvar и живёт вне always-блоков:

genvar i;
generate
    for (i = 0; i < 8; i = i + 1) begin : g
        bit_inverter inv(.x(in[i]), .y(out[i]));
    end
endgenerate

Это штампует 8 instance bit_inverter (разбираем в Module Instantiation). Это структурно - ты говоришь "сделай 8 копий этого подмодуля" - а не behavioral.

Краткое различие:

  • Процедурный for (внутри always) - разворачивает операторы внутри одного behavioral-блока.
  • Generate for (вне always) - размножает целые структурные конструкции: instance, assign-операторы, именованные блоки.

Используй ту, что соответствует тому, что размножаешь.

Когда for блистает: векторные операции

Циклы лучше всего показывают себя, когда делаешь одну и ту же операцию над каждым битом вектора. Population count, parity, реверс байт, генерация lookup-таблиц:

32 итерации, каждая делает одно битовое присваивание - гораздо читаемее, чем выписывать 32 wire-присваивания вручную. Синтезатор разворачивает чисто.

while, repeat, forever

Кроме for, у Verilog ещё три конструкции цикла - в основном для testbench:

// Прогон, пока условие истинно
while (~done) begin
    @(posedge clk);
    cycles = cycles + 1;
end

// Прогон N раз - проще, чем for, когда счётчик не нужен
repeat (8) @(posedge clk);

// Вечно - генераторы clock, циклы мониторинга
always #5 clk = ~clk;
forever begin
    @(posedge clk);
    $display("count=%0d", count);
end

while, repeat и forever синтезируются только в узких случаях (особенно repeat с константным счётом и тактируемым телом). В testbench - полезные инструменты; в синтезируемом RTL предпочитай счётный for плюс явный state machine.

Процедурный for в testbench

В testbench for-циклы ведут себя так, как ведёт себя софт. Используй свободно:

Вложенные циклы прогоняют каждую комбинацию двух 2-битных входов. Симулятор выполняет итерации последовательно. Никаких забот про разворот - testbench не синтезируются.

Частые ошибки

for-цикл в синтезируемом коде с неконстантной границей. Синтезатор отвергнет. Если граница runtime - строй счётчик и state machine.

Забывают, что тело цикла становится параллельным железом. 64-итерационный цикл с умножителем в теле - это 64 параллельных умножителя, скорее всего не то, что ты хотел. Для широких datapath строй один умножитель и подавай ему последовательно.

Смешивают integer i и reg под именем i. Это разные scope; integer побеждает внутри цикла. Выбирай ясные имена, чтобы избежать путаницы.

Что дальше

Теперь у тебя все процедурные конструкции, которые предлагает Verilog. Следующая глава собирает это в паттерны, которые цифровые разработчики реально отправляют: Clocked Logic - flip-flop, регистры и pipelines - и Finite State Machines - стандартная идиома для любого контроллера с несколькими режимами работы.

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

Как работают for-циклы в Verilog?

Синтаксически выглядят как в C: for (i = 0; i < N; i = i + 1) statement;. Но для синтезируемого кода цикл разворачивается на elaboration - синтезатор разворачивает его в N копий тела. В железе нет runtime-счётчика и нет зацикливания. В testbench for-циклы ведут себя как софтовые родственники, потому что симулятор может шагать через них последовательно.

For-цикл синтезируется в Verilog?

Да, но только когда границы цикла - константы, известные на elaboration. Синтезатор разворачивает цикл в N параллельных копий тела. Если границы зависят от runtime-сигнала, цикл не синтезируется - придётся конвертировать в тактируемый последовательностный дизайн.

В чём разница между for и generate for в Verilog?

for-цикл внутри always-блока - это процедурная конструкция, синтезируется разворачиванием. generate for-цикл (с genvar) - явная конструкция времени elaboration, которая штампует структурное железо: несколько instance модулей, несколько wires, несколько assign-операторов. Используй for внутри процедурных блоков; используй generate for снаружи них для размножения структуры.

Есть ли в Verilog while-цикл?

Да - while (condition) statement;. Синтезируется только когда синтезатор может доказать, что цикл завершится за ограниченное число итераций. На практике это редко, так что while появляется в основном в testbench и simulation-only-коде. Для синтезируемой итерации используй счётный for.

Coddy programming languages illustration

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

НАЧАТЬ