Сегодня я столкнулся с очень необычным фрагментом тестового кода, и все были в недоумении, почему он работает именно так. Упрощенная версия будет выглядеть примерно так (обратите внимание, что тест написан внутри task
):
task TEST();
logic [9:0] a;
assign a = 10'd512;
#10ns;
assign a = a/2;
#100ns;
endtask
Мои вопросы:
Ожидалось, что a
будет продолжать делиться на 2 каждую единицу дельты времени, пока его значение не достигнет 0
. Но вместо этого при моделировании (с использованием Xcelium) оно делится только один раз, и конечный результат равен a = 256
. Почему это? Коллега подтвердил с помощью небольшого теста, что assign
работает нормально в задаче, если RHS не содержит переменную LHS. Поэтому я предполагаю, что это как-то связано с тем, что по сути это блок Always@(...), где единственной переменной в списке чувствительности является также переменная LHS, но я нигде не могу найти описание того, что происходит в этом конкретном случае. .
Я был озадачен тем, почему код компилируется успешно и даже без каких-либо предупреждений, имея 2 оператора assign
для одной и той же переменной. В поисках ответа я обнаружил, что в разделе 10.6.1 версии LRM 1800-2017 говорится:
Если ключевое слово Assign применяется к переменной, для которой уже существует процедурное непрерывное присваивание, то это новое процедурное непрерывное присвоение должно отменить назначение переменной перед выполнением нового процедурного непрерывного присвоения.
И теперь я еще больше запутался: почему LRM говорит это, если наличие двух операторов assign
для одной и той же переменной обычно приводит к ошибкам компиляции (несколько драйверов) в модуле вместо сохранения последнего оператора assign
и игнорирования остальных?
Семантика планирования непрерывных назначений четко не определена в LRM. Между оценкой RHS и обновлением LHS нет разницы во времени — они оба происходят в одной и той же активной области. Обратите внимание, что обновление может произойти немедленно или быть помещено в очередь. LRM не определяет порядок событий внутри региона, хотя используется термин «очередь». Это дает инструментам свободу выполнять обновление перед планированием чувствительности RHS.
Вы путаете структурные непрерывные задания с процедурными непрерывными заданиями. Это обычное явление, поскольку обе конструкции используют одно и то же ключевое слово assign
.
В сети может быть несколько непрерывных присвоений (драйверов), но только одно непрерывное присвоение переменной.
Процедурное непрерывное присвоение переопределяет все процедурные присвоения переменной и заменяет любое предыдущее процедурное непрерывное присвоение этой переменной.
Процедурные непрерывные присваивания изначально предназначались для моделирования асинхронного поведения отдельного процесса от синхронного поведения триггера. Однако большинство инструментов синтеза решили ограничить моделирование триггера одним (always
) процессом. Эта конструкция сейчас используется редко.
Спасибо, Дэйв. Я чувствую, что это отвечает на мой второй вопрос, но я не уверен насчет первого. Процедурный непрерывный оператор по-прежнему является «непрерывным», так почему же assign a = a/2
не вызывает бесконечный цикл обновления значения? Я предполагаю, что такой код является плохой практикой, и удивлен, что он не вызывает ошибки/предупреждения (есть ли что-то специально запрещающее такой код?). Должен ли результат, который я описал в своем вопросе, быть записан как реакция конкретного инструмента на плохую практику кодирования, или есть какое-то связанное с SV объяснение того, что значение обновляется только один раз?
Операторы процедурного назначения и операторы
deassign
считаются устаревшими, IEEE1800-2023 C.4.2. Но его считали устаревшим, начиная с IEEE1800-2005 25.4, так что кто знает, сколько времени пройдет, прежде чем он фактически устареет.