Софтовый двойник
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.