Menu

Case-оператор в Verilog: правильная multi-way декодировка

Как работает case для чистого multi-way декодирования, default, который никогда не стоит пропускать, и различия между case, casex и casez.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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 избыточен. На практике:

  1. Синтезатор не всегда доказывает, что ветки исчерпывающие.
  2. Селектор может быть x или z в симуляции, что не совпадает ни с одним явным case.
  3. Добавление новой ветки позже может оставить дефолтное поведение неопределённым.

Всегда пиши 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.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ