assign описывает постоянную истину
Объявление wire создаёт сигнал. assign описывает что его приводит. Отношение непрерывное: что бы ни было справа, левая часть равна ему в каждый момент симулированного времени:
wire y;
assign y = a & b;
Две вещи, заложенные в этих двух строках:
yсуществует как wire в схеме.yв любой момент равен побитовому ANDaиb. Меняешь любой вход -yследует.
Нет clock и нет события, запускающего обновление. Симулятор видит, что a или b поменялись, помечает y "грязным" и переоценивает выражение. В железе это пара AND-вентилей: комбинационно, без состояния, мгновенно.
Неявная форма
Можно совместить объявление и присваивание в одной строке:
wire y = a & b;
То же, что две строки выше. Полезно для inline-wires, до которых никому снаружи этого scope нет дела. Многие style guides предпочитают неявную форму - она ставит объявление прямо рядом с уравнением.
Что assign может приводить
Цель assign должна быть net-типом - в чистом Verilog это wire (или один из более редких родственников вроде tri, wand, wor). Не может быть reg. Если случайно объявил цель как reg:
reg y;
assign y = a & b; // ОШИБКА: нельзя приводить reg через assign
Компилятор скажет. Либо меняй цель на wire, либо переноси логику в always @(*), где reg - легальная цель.
Правой частью может быть всё, что вычисляется в значение: литералы, сигналы, parameters, выражения с операторами, вызовы функций. Можно смешивать разные ширины входов; применяются стандартные правила расширения.
Когда использовать assign vs always
Оба могут давать комбинационную логику. Выбор в основном про то, как читается код:
assignлучше, когда отношение - одно выражение. Adder'ы, простые mux через?:, маски, parity-биты - всё, что пишется в одну строку.always @(*)лучше, когда нужны процедурные операторы. Многоветочныеcase-операторы, вложенныеif/else if, что-то, что выигрывает от именованных промежуточныхreg. Это разбираем в Always Block.
Вот один и тот же 4-to-1 mux в обоих стилях:
Оба модуля синтезируются по сути в один и тот же мультиплексор. Версия с assign - одна строка кода; версия с always - шесть. Для четырёх случаев близко; для шестнадцати - case-блок явно читаемее.
Частые паттерны
Простая комбинационная логика
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
Одно выражение, один wire. Хлеб и масло assign.
2-to-1 mux
assign out = sel ? a : b;
Одно conditional выражение - синтезатор превращает в один 2-to-1 mux. Самый чистый способ написать "выбери между a и b".
Bit packing
assign status = {error, overflow, ready, busy, 4'b0};
Concatenation справа от assign - способ упаковать флаги в status-байт. Результат вычисляется и приводится непрерывно.
Tri-state выход
assign data_pin = output_enable ? data_out : 1'bz;
Когда output_enable high - приводим pin. Low - отпускаем в high-impedance. Канонический паттерн на pin'ах чипа, где несколько driver могут делить wire.
Параллельность: несколько assign - это не последовательность
Напоминание, которое не перестаёт быть актуальным: несколько assign-операторов в одном module все работают параллельно. Это не последовательность:
assign y = a & b; // существует всегда
assign z = a | b; // тоже существует всегда, независимо
Порядок в файле не имеет значения. Оба уравнения одновременно истинны. Синтезатор может разместить AND-вентиль слева от OR-вентиля или справа - неважно, оба вентиля работают непрерывно.
Если хочешь последовательно выглядящего поведения, тянись к always (и, скорее всего, к clock). Это другая глава.
Несколько driver: bus-паттерн
У wire может быть больше одного assign, направленного на него, но почти никогда этого не хочешь, кроме tri-state-шин. Два driver, дерущиеся за wire, дают неопределённое поведение:
assign y = a;
assign y = b; // ПЛОХО - два driver, симулятор выбирает один или X-ит
Легитимный паттерн: каждый driver отпускает в z, когда неактивен, и в любой момент активен максимум один.
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
Это работает, потому что в любой момент максимум одна из двух тернарок даёт не-z значение. Реальное значение wire - то, которое не отпустил тот driver, что активен.
На внутренней логике - везде, кроме pin'ов чипа или общих on-chip шин - один driver на wire. Многодрайверные баги - кошмар при отладке.
Что assign не может
Кое-что, для чего assign - неправильный инструмент:
- Хранилище.
assignописывает комбинационные отношения; он не может создать flip-flop. Если значение нужно помнить между тактами - это блокalways @(posedge clk). - Многошаговая процедурная логика. Нельзя писать
if/elseилиcaseвнутриassign. Самое близкое - цепочка?:, которая становится уродливой после трёх веток. - Приведение регистров изнутри процедурного блока. Цели
regтребуют процедурного присваивания, неassign.
Знание границ - это то, как ты понимаешь, когда переключаться на always.
Что дальше
Ты теперь видел всю структурную сторону Verilog: объявление модулей, инстанцирование их и подключение комбинационной логики через assign. Следующая глава переходит в процедурные блоки - конструкции initial и always, где начинают важничать время и порядок.
Часто задаваемые вопросы
Что такое continuous assignment в Verilog?
assign target = expression; объявляет постоянное непрерывное отношение: target всегда равен expression. Когда любой сигнал в expression меняется, симулятор переоценивает правую часть и обновляет target. Никаких clock, никаких событий - отношение истинно в любой момент времени.
Что можно задать через assign в Verilog?
assign может приводить wire, но никогда reg. Цель должна быть net-типом. Если хочешь присваивать чему-то внутри always, объяви как reg. Компилятор отвергнет assign x = ..., если x - reg, и отвергнет x = ... внутри always, если x - wire.
Когда использовать assign, а когда always-блок?
Используй assign для простой комбинационной логики - одно выражение на входе, один сигнал на выходе, не нужен if/else. Используй always @(*), когда логике нужны процедурные операторы (case, цепочка if/else if, цикл for). Оба дают комбинационное железо; выбор - про читаемость.
Можно ли иметь несколько assign на один wire в Verilog?
Только если моделируешь tri-state-шину, где каждый driver отпускает wire в z, когда неактивен. Два assign, пытающихся приводить wire к определённым значениям одновременно, дают contention - симулятор может выбрать один, может X-нуть сигнал, зависит от тула. Для обычной комбинационной логики - один driver на wire.