Two-state vs four-state логика
В софте бит - это 0 или 1. В Verilog бит может быть одним из четырёх значений:
0- wire приведён к low.1- wire приведён к high.x- значение wire unknown. Симулятор не может определить.z- wire high-impedance. Ничто не приводит его в действие.
Эта four-state-модель существует, потому что у железа такая же проблема. Реальный wire может быть притянут к low, к high, undefined (приводится двумя источниками, которые борются) или плавающим (никакого driver). Симулятор должен моделировать все четыре, чтобы быть полезным.
Как появляется x
Запусти это и посмотри вывод:
a объявлен, но никогда не записан, так что он сидит в x. x + 5 даёт x - любая арифметика с unknown даёт unknown. На выходе будет aaaa иксов.
Частые источники x в твоих проектах:
reg, объявленный, но никогда не сброшенный (большинство синтезируемого Verilog использует явный reset, чтобы их обнулить).case-оператор безdefault, в который попало значение, ни одной ветке не подошедшее.wire, который потерял свой единственный driver после рефакторинга.- Цепочка
if/else, где одна ветка не присваивает сигнал, который присваивает другая (latchx, если не покрыто). - Чтение за пределами вектора или массива.
Распространение X: один бит x ломает всё
Жестокая штука с x - он распространяется. Один бит x в операнде превращает весь результат в x:
Заметь, что 0 & x - это 0 (AND с 0 всегда даёт 0), а 1 | x - это 1 (OR с 1 всегда даёт 1). Симулятор побитово пессимистичный, но всё равно уважает identities. Арифметика и сравнение не так щедры.
Вот почему один неинициализированный регистр может превратить всю шину выхода в xxxx. Трасируй назад от любого x - найдёшь источник.
Как появляется z
z - значение wire, который никто не приводит:
Два паттерна в этом фрагменте:
floatingпросто объявлен и никем не приводится. Дефолтноz.data_out- намеренный tri-state. Когдаenablelow, выход явно отпускается вz. Так bus driver "отпускает", чтобы другой driver мог взять.
На внутренней логике z почти всегда ошибка. На двунаправленном pin или общей шине z - ровно то, что надо.
Сравнение через == vs ===
Обычный оператор равенства == возвращает x, когда у любого операнда есть бит x или z:
=== (и его пара !==) делает строгое побитовое сравнение, включая x и z. Используй его всегда, когда нужно проверить на или против x/z в testbench. === не синтезируется, но внутри блока initial в testbench это неважно.
Системная функция $isunknown(expr) - самый чистый способ спросить "есть ли в этом выражении биты x или z?" - возвращает 1, если да, 0, если нет.
Использование x как намеренного don't-care
Контроверсиальный, но легитимный паттерн: 'x в default-ветке state machine говорит синтезатору "это состояние недостижимо, оптимизируй свободно":
case (state)
IDLE: next_state = go ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = 'x; // недостижимо
endcase
Синтезатор может использовать x, чтобы слить состояния и сократить количество вентилей. В симуляции, если твои рассуждения были неверны и default всё-таки срабатывает, увидишь, как x распространяется от next_state, и баг становится виден сразу.
Используй это только когда продумал, действительно ли default недостижим. Если нет - ставь default в безопасное состояние.
Стандартный рецепт отладки
Смотришь на waveform, полный x. Рецепт:
- Найди самый ранний
x. Иди назад по waveform во времени. Самый ранний сигнал, ставшийx, ближе всего к источнику. - Найди его driver. Открой исходник. Что присваивает этому сигналу?
assign? Блокalways? - Проверь входы driver. Если RHS у driver содержит
x, propagation делает своё дело - баг выше по потоку. - Если у driver чистые входы, но он выдаёт
x, значит driver неполный.caseбез default,ifбезelse, регистр без reset.
Большинство x-storm багов сводится к одному из: пропущенный reset, пропущенный default или подмодуль, который не подключён.
Что дальше
Теперь у тебя полная история про типы данных: wire/reg, векторы, parameters, числовые литералы и four-state-логика. Следующая глава начинает использовать всё это для построения выражений - операторы всех мастей, включая bit-level операторы, которые в софте не имели бы смысла.
Часто задаваемые вопросы
Что означает x в Verilog?
x - это unknown значение. Сигнал, который равен x, может быть и 0, и 1 - симулятор не может определить. Появляется, когда сигнал не приведён, когда два driver конфликтуют, когда регистр читают до reset или везде, где undefined-поведение иначе тихо распространялось бы. Относись к x как к сигналу бага: он почти никогда не значит то, что ты хочешь.
Что означает z в Verilog?
z - это high-impedance: wire вообще не приводится. Это легитимное состояние tri-state-выходов (data buses, двунаправленных pins), но на внутренних сигналах z обычно ошибка, означающая 'тут ничего не подключено'. Синтезаторы отвергают большинство паттернов с z вне явных tri-state output enables.
Почему мой Verilog-выход xxxx?
Почти всегда потому, что сигнал ничем не приводится, или операция распространила x от другого сигнала. Иди назад: какой сигнал x, что его кормит, активен ли driver? Частые виновники - пропущенные default в case-операторах, регистры без reset и wires, которые потеряли driver после рефакторинга.
Как проверить x или z в Verilog?
Используй оператор ===, который сравнивает бит-в-бит, включая x и z. a === 1'bx истинно, когда a реально x. Обычное == возвращает x, когда у любого операнда есть бит x, так что a == 1'bx никогда не даст нужного ответа. Есть ещё $isunknown(a) - аккуратный boolean.
Можно ли присваивать x как default в Verilog?
Да, и это умышленная техника в case-операторах: default: out = 'x; говорит синтезатору 'обещаю, эта ветка никогда не срабатывает, оптимизируй свободно'. Цена - если она всё-таки сработает в симуляции, x распространится и баг проявится. Используй это, когда уже доказал, что default недостижим, а не как способ не писать ветку.