Знакомый синтаксис, другая ментальная модель
if/else выглядит ровно как в C:
if (condition) begin
// ... операторы ...
end else begin
// ... операторы ...
end
Но правила другие, потому что Verilog не софт. Две вещи держим в голове:
if/elseживёт только внутри процедурного блока. Нельзя написать отдельностоящийifна уровне module.- Во что синтезируется
if/else, зависит от типа блока. В комбинационном блоке - в mux или priority-encoder. В тактируемом - во flip-flop с условной логикой обновления.
Внутри комбинационного блока
Комбинационный always @(*) перезапускается, когда меняется a, b или c. Цепочка if/else выбирает одну ветку, присваивает max, и блок заканчивается. Поскольку max всегда присваивается (на каждом пути), синтезатор выдаёт чистую комбинационную логику - без latch.
Заметь, что max объявлен reg, хотя в железе нет flip-flop. То же правило, что всегда: всё, что присваивается внутри always, должно быть reg.
Latch-ловушка
Самый частый баг в комбинационном коде новичка:
// НЕПРАВИЛЬНО - выводит latch
always @(*) begin
if (enable)
out = data;
// нет else! когда `enable` low, что делает `out`?
end
Синтезатор читает "когда enable low, out не присваивается" и решает, что out должен помнить старое значение. Помнить значение требует ячейку памяти, и тул вставляет latch. Latch в синхронных проектах вызывают timing-проблемы, их сложно сбрасывать и они почти никогда не то, что ты имел в виду.
Два способа фикса:
Оба дают одно и то же комбинационное железо - 2-to-1 mux. Паттерн "дефолт в начале" лучше масштабируется, когда у одного сигнала много условных присваиваний.
Внутри тактируемого блока
Замечай, что отличается от комбинационного случая:
- Блок -
always @(posedge clk)- территория flip-flop. - Присваивание через
<=(non-blocking). - Нет
elseдля случая "ни reset, ни enable". Это нормально. В тактируемом блоке, когда ни одна ветка не срабатывает, flip-flop просто держит старое значение - что физически делает flip-flop. Никакой latch не выводится, потому что сигнал уже регистр.
Это единственное место, где опустить else безопасно. Вне тактируемых блоков всегда обрабатывай каждый путь.
Цепочка else if: priority encoder
Цепочка else if-операторов имеет неявный приоритет - более ранние условия перекрывают более поздние:
requests[0] - высший приоритет: если он установлен, grant равен 0 независимо от того, что делают старшие биты. Синтезатор превращает цепочку в каскад mux: сначала проверяем бит 0, потом 1, потом 2, потом 3. Каждый уровень добавляет небольшую задержку.
Если условия взаимоисключающие - скажем, декодируешь one-hot вход - case (следующий документ) даёт более плоское и быстрое железо, чем цепочка else if. Используй форму case, когда нет реальной потребности в приоритете.
if без else в тактируемом коде
Тактируемому блоку не нужен else, потому что "держать старое значение" - дефолтное поведение. Так строят enables:
always @(posedge clk) begin
if (load) target <= incoming;
// нет else: когда load low, target держит значение
end
Это load-enabled-регистр. Большинство pipeline-регистров, счётчиков и конфигурационных регистров используют этот паттерн.
begin/end и одиночные операторы
Как в C, для одного оператора begin/end можно опустить:
if (a) out = 1;
else out = 0;
Для чего-то длиннее одного оператора - используй блок:
if (a) begin
out = 1;
flag = 1;
end else begin
out = 0;
flag = 0;
end
Два паттерна свободно смешиваются. Style guides обычно рекомендуют всегда использовать begin/end, чтобы добавлять второй оператор было безболезненно.
Что дальше
Следующий документ - Case Statement - разбирает case, правильный инструмент для multi-way декодирования (state machines, диспетчеризация opcode, ROM-таблицы). После - циклы for, чуть отличающиеся от софтовых родственников, потому что разворачиваются на elaboration.
Часто задаваемые вопросы
Как работает оператор if в Verilog?
if (cond) statement; выполняет statement, когда cond non-zero. Можно обернуть несколько операторов в begin ... end. Добавь else statement; для альтернативной ветки или объедини с else if (other_cond) .... if/else существует только внутри процедурных блоков - initial или always - не на верхнем уровне module.
Что такое inferred latch в Verilog?
Latch, который синтезатор создал, когда ты его не просил, потому что твой комбинационный always-блок не присвоил сигнал в каждом пути. Тул видит 'if a then out = 1' без else, решает, что неприсвоенный случай должен помнить старое значение, и выдаёт latch. Latch почти всегда неправильны; фикс - давать каждому сигналу дефолтное значение в начале блока или явный else.
Как избежать inferred latch в Verilog?
В комбинационном always @(*)-блоке убедись, что каждый выходной reg присвоен на каждом пути. Самый чистый паттерн - выставить дефолты в начале блока и потом переопределять условно. Компилятор обычно предупреждает, когда выводит latch - относись к warning как к ошибке.
Во что синтезируется цепочка if-else в Verilog?
В priority encoder. Первый if имеет высший приоритет, следующий else if проверяется только если первый false, и так далее. В железе это становится цепочкой mux с зашитым порядком приоритета. Если условия взаимоисключающие, case-оператор с той же логикой часто синтезируется в более плоское железо и читается чище.