Multi-way ветка
case - плоский dispatch-конструкт Verilog. Даёшь ему выражение - он выбирает совпавшую ветку:
case (expression)
pattern_1: statement_1;
pattern_2: statement_2;
pattern_3: begin
statement_3a;
statement_3b;
end
default: default_statement;
endcase
Структурно похож на switch в C, но:
- Никакого
break- каждая ветка неявно завершается следующей. - Паттерны могут быть векторами, не только целыми.
- Синтезатор превращает всё это в плоский mux (или one-hot-дешифратор, когда паттерны это позволяют).
Разобранный пример: 4-to-1 mux
Тело case имеет четыре явные ветки плюс default. Синтезатор видит 2-битный вход, отображённый на одно из четырёх значений, и выдаёт 4-to-1 мультиплексор. Чисто, плоско, быстро.
Почему нужен default
Для комбинационного case опустить default - та же ловушка, что if без else: любое несовпавшее значение оставляет out неприсвоенным, и синтезатор выводит latch.
Для 2-битного sel паттерны выше покрывают все четыре возможных значения - теоретически default избыточен. На практике:
- Синтезатор не всегда доказывает, что ветки исчерпывающие.
- Селектор может быть
xилиzв симуляции, что не совпадает ни с одним явным case. - Добавление новой ветки позже может оставить дефолтное поведение неопределённым.
Всегда пиши default. Для state machines и mux-логики, где знаешь, что default недостижим, присваивание 'x:
default: out = 8'bx;
…говорит синтезатору "это don't-care, оптимизируй свободно" и выводит яркий красный x в симуляции, если недостижимая ветка всё-таки случилась. Лучшее из двух миров.
State machine в case
Классическое использование case - логика переходов состояний конечного автомата:
Блок case (state) - логика переходов состояний. Каждая ветка решает, какое следующее состояние и сколько в нём оставаться. default тут недостижим (мы покрыли RED/GREEN/YELLOW исчерпывающе в 2-битном пространстве), но он на месте как страховка - если state каким-то образом станет 2'd3, FSM чисто сбросится в RED, а не залатчится.
Finite State Machines копает в этот паттерн глубже.
Несколько паттернов на одну ветку
Можно перечислить несколько паттернов, делящих один оператор, через запятую:
case (opcode)
4'h0, 4'h1, 4'h2: result = a + b;
4'h3, 4'h4: result = a - b;
4'h8: result = a & b;
default: result = 8'd0;
endcase
Это два opcode, оба означающих "вычитать", три - "сложить". Синтезатор OR-ит паттерны вместе для компаратора.
casez и casex: don't-care матчинг
Иногда хочется матчить паттерн с некоторыми битами неуказанными - "любой opcode, начинающийся с 010":
casez (opcode)
8'b010?_????: instruction = ALU_OP;
8'b110?_????: instruction = LOAD_OP;
8'b1110_????: instruction = JUMP_OP;
default: instruction = UNKNOWN;
endcasez
casez трактует ? (и z) в паттерне как don't-care. Каждый ? совпадает с 0 или 1. Полезно для декодирования форматов инструкций, где некоторые позиции бит не используются для определённых классов opcode.
casex расширяет это, чтобы трактовать и x как don't-care. casex опасен, потому что неинициализированные сигналы (которые x в симуляции) совпадают с каждой веткой, давая удивительное поведение. Большинство современных style guides рекомендуют casez и запрещают casex.
В SystemVerilog есть ещё case inside - самая чистая версия из всех, принимает диапазоны и списки - но чистый Verilog заканчивается на casez.
case vs цепочка if/else if
Обе конструкции могут выразить multi-way решения, но синтезируются в разное железо:
case- плоский dispatch. Синтезатор может выдать one-hot-дешифратор, сбалансированное mux-дерево или другие плоские структуры. Постоянное время оценки.if/else if- приоритетная цепочка. Синтезатор выдаёт каскад, где каждый уровень добавляет задержку. Логарифмически медленнее.
Функционально перекрываются. Стилистически: используй case, когда условия - о значении одного выражения. Используй if/else if, когда есть настоящий приоритет или условия завязаны на разные сигналы.
// Лучше как case:
if (sel == 2'd0) out = a;
else if (sel == 2'd1) out = b;
else if (sel == 2'd2) out = c;
else out = d;
// Лучше как if/else if:
if (urgent_event) next_state = HANDLE_URGENT;
else if (timer_expired) next_state = TIMEOUT;
else if (data_ready) next_state = PROCESS;
else next_state = state;
Первые три условия все спрашивают "что такое sel?" - case читается естественнее и синтезируется площе. Вторые три - независимые события с очевидным приоритетом - if/else if лучше подходит.
Что дальше
Последний документ в этой главе - For Loops - разбирает for Verilog и удивительную штуку, которая случается, когда используешь его в синтезируемом коде. Потом всерьёз переходим в последовательностную логику и FSM.
Часто задаваемые вопросы
Что такое case-оператор в Verilog?
case (expr) ... endcase - это multi-way ветка в Verilog. Он вычисляет выражение один раз и переходит на ту ветку, которая совпала. Это идиоматический выбор для state machines, дешифраторов opcode, селекторов mux и всего, что выбирает между несколькими взаимоисключающими опциями.
В чём разница между case, casex и casez в Verilog?
case матчит побитово точно, включая значения x и z. casez трактует z (и ?) в case-паттернах как don't-care. casex трактует и x, и z как don't-care. Don't-care-матчинг полезен для opcode-паттернов, где некоторые позиции бит не важны, но casex опасен в симуляции, потому что неинициализированные сигналы (x) могут случайно совпасть с каждой веткой.
Зачем нужен default в case-операторе Verilog?
Без default синтезатор видит возможность, что ни одна ветка не совпала, решает, что выходной сигнал должен сохранить старое значение, и выводит ненужный latch. Ветка default обрабатывает каждое неприсвоенное значение - обычно установкой выхода в безопасное значение или пометкой ветки недостижимой через присваивание x. Всегда включай его.
Когда использовать case vs if-else в Verilog?
Используй case, когда условия взаимоисключающие и диспатчишь по значению одного выражения - state machines, дешифраторы opcode, селекторы mux. Используй if/else, когда есть настоящий порядок приоритета или условия завязаны на разные сигналы. case синтезируется в более плоское и быстрое железо, чем длинная цепочка else if.