Рабочая лошадка behavioral-Verilog
assign описывает комбинационную логику из одного уравнения. Как только нужны if/else, case или память - тянешься к always. Always-блок - это процедурный код, который перезапускается, когда меняются определённые сигналы. Сигналы, инициирующие перезапуск, - это sensitivity list.
Чаще всего встретишь две формы always:
always @(*)- перезапускается, когда любой читаемый в блоке сигнал меняется. Строит комбинационную логику.always @(posedge clk)- перезапускается только на нарастающем фронтеclk. Строит тактируемую последовательностную логику (flip-flop).
Существуют и другие формы (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)), но две выше покрывают почти каждый синтезируемый блок, который ты напишешь.
Комбинационный always @(*)
Три вещи, которые стоит заметить:
out- этоreg. Всё, чему присваивают внутриalways, должно бытьreg. Ключевое слово не значит "это flip-flop"; здесь оно просто значит "пишу из процедурного блока".always @(*).*говорит "просыпайся, когда меняется всё, что я читаю". Симулятор сам выводит sensitivity list. Можно написать список вручную -always @(sel)- но@(*)безопаснее, потому что пропущенный сигнал - классический источник багов.- Никакого clock. Этот блок описывает комбинационную логику. Синтезатор выдаёт кусок логики, который вычисляет
outизselнапрямую - без flip-flop, без clock-пина.
Случай default не опционален по сути, хотя опционален по синтаксису. Опусти его - и любое неуказанное значение оставит out со старым значением, что синтезируется в нежелательный latch. Всегда включай default.
Последовательностный always @(posedge clk)
Ключевые отличия от комбинационной версии:
always @(posedge clk). Блок перезапускается только на нарастающем фронтеclk. Между фронтами ничего не происходит.- Non-blocking присваивание
<=. Внутри тактируемого блока это правильный оператор. Он говорит "запланируйcountпринять новое значение в конце временного шага", что и есть поведение flip-flop. Почему и альтернатива - в Blocking vs Non-blocking. defaultне нужен.ifпокрывает обе ветки (reset и не-reset). Риска latch нет.
Синтезатор видит этот силуэт - тактируемая sensitivity, non-blocking присваивание - и выдаёт 4-битный регистр (четыре flip-flop) плюс комбинационную логику, считающую count + 1, и mux, выбирающий между reset и инкрементом.
Разница для синтеза
Один и тот же исходник module может описать два совершенно разных куска железа в зависимости от формы always-блока:
| Блок | Железо |
|---|---|
always @(*) y = expr; | Чистая комбинационная логика. Без памяти. |
always @(posedge clk) y <= expr; | Flip-flop. Захватывает expr раз за такт. |
always @(*) if (en) y = expr; | Latch - обычно баг. "Else"-ветка сохраняет старое значение. |
always @(posedge clk) if (en) y <= expr; | Flip-flop с enable. Захватывает только когда en high. |
Третий случай - latch-ловушка. Latch - прозрачная ячейка памяти, которая держит свой выход, когда вход не выставлен - полезен в специфических проектах, почти всегда баг при случайном появлении. Большинство синтезаторов громко предупреждают, когда выводят latch, который ты не просил. Относись к warning как к ошибке.
Варианты sensitivity list
Встретишь несколько менее частых:
always @(a or b or c)- явный список. Verilog-2001 добавил разделитель,:always @(a, b, c). Работает и то, и то.always @(posedge clk or negedge reset_n)- асинхронный reset. Блок срабатывает на нарастающем фронте clock или ниспадающем фронте reset. Используется, когда reset должен сработать немедленно, не дожидаясь следующего clock.always @(negedge clk)- тактирование по падающему фронту. Редко; некоторые проекты используют для "negative-edge-triggered" flip-flop, захватывающих на падающем, а не на нарастающем.
Для новых проектов предпочитай always @(*) для комбинационной и always @(posedge clk) для последовательностной. К асинхронному reset тянись только когда дизайн реально требует.
Два блока - два куска железа
Несколько always-блоков в одном module независимы - каждый становится своим куском железа:
Тактируемый блок даёт flip-flop-регистр. Комбинационный - XOR-вентиль. Они живут бок о бок; ни один не знает о другом. Два выхода меняются по совершенно разным графикам.
Что always-блоки не могут
Несколько вещей, выглядящих заманчиво, но запрещённых:
- Присваивать
wire: цель должна бытьreg. Компилятор обеспечивает. - Присваивать одному
regиз двух разныхalways-блоков: даёт undefined-поведение в симуляции и не синтезируется. Один driver на сигнал. - Читать и писать один и тот же сигнал в одном комбинационном блоке так, чтобы создать петлю обратной связи:
always @(*) x = x + 1;- это zero-delay-петля, которую симулятор не может разрешить.
Первые два ловит компилятор. Третье иногда проявляется только в момент симуляции как зависание.
Что дальше
Следующий документ - Initial Block - разбирает родственника always: блок, который срабатывает ровно один раз при старте симуляции. Это рабочая лошадка testbench. После - правила blocking vs non-blocking, которые определяют, делает ли твой тактируемый блок то, что ты хотел.
Часто задаваемые вопросы
Что такое always-блок в Verilog?
always вводит процедурный блок, который перезапускается каждый раз, когда сигналы в его sensitivity list меняются. Есть два вида: always @(*) строит комбинационную логику (перезапускается при любом изменении входов), а always @(posedge clk) строит последовательностную (перезапускается на каждом нарастающем фронте clk). Тело always-блока может содержать if, case, for и процедурные присваивания.
В чём разница между always @(*) и always @(posedge clk)?
always @(*) чувствителен к любому сигналу, читаемому в блоке; даёт комбинационную логику без памяти. always @(posedge clk) чувствителен только к нарастающему фронту clk; даёт flip-flop, захватывающие состояние раз за такт. У первого нет clock и нет регистра; у второго есть оба.
Что такое sensitivity list в Verilog?
Список сигналов после @, определяющий, когда always-блок перезапускается. @(*) - shorthand для 'каждый сигнал, читаемый в блоке'. @(posedge clk) срабатывает только на нарастающем фронте clk. @(posedge clk or negedge reset_n) срабатывает на любом из событий - для асинхронных reset. Неправильный sensitivity list - один из самых частых источников несовпадения симуляции и синтеза.
Можно ли присвоить wire внутри always-блока?
Нет. always-блоки могут присваивать только reg (или logic в SystemVerilog). Компилятор это обеспечивает. Если хочешь, чтобы wire был выходом процедурной логики, объяви промежуточный reg, приводи его внутри always и assign-ни wire от reg снаружи - или просто меняй wire на reg.