Jump to content

    

Recommended Posts

Всем привет!

 

Вопрос по планировщику симулятора. Пример привожу примитивный, чтобы только принцип продемонстрировать. Вот предположим есть код в двух комбинационных блоках:

 

logic a = 0;
logic b = 0;

always_comb begin
  a = 0;
  if(<true_cond>)
    a = 1;

   if(b) ...
end
  
always_comb begin
  b = 0;
  if(a) begin
    b = 1;
end
  

По логике работы симулятора получается, что в первом блоке переменная a даёт результат 1, при этом она меняет значение: на входе 0, после условия 1, т.е. как бы происходит событие (event). Это событие триггерит второй блок, где с переменной b происходит аналогичная картина - т.е. изменение переменной b, что есть тоже событие, и это событие должно триггернуть первый блок, т.к. там есть ссылка на b в условии if. Но прогон (evaluation) первого блока снова сгенерит событие (т.к. a поменялась внутри блока), которое запустит второй блок. И так далее без конца.

 

На практике такой простой код, однако, не вызывает этого бесконечного цикла - очевидно, симулятор как-то "соображает", что реально-то события не должно возникать. Но вот у меня в рабочем проекте это тем не менее произошло. Там код не такой простой, но схематично аналогичный: вместо первого блока там описание логики переходов FSM, в другом - сигналы, которые используют fsm_next значение.

 

Реально петли по сигналам там нет - та же Vivado не находит при синтезе. Т.е. как и в примере выше, сигналы a и b не образуют петлю, т.к. b на a не влияет, просто описана в том же блоке. И насколько понимаю, если бы симулятор анализировал переменную при прогоне блока только в конце по её результирующему значению, он бы "увидел", что реально переменная не меняется и поэтому события возникать не должно. 

 

Вот тут аналогичная проблема, и там посоветовали использовать для симулятора опцию командной строки -delay_trigger (там кэденсовский симулятор), которая, как объяснил советующий, заставляет симулятор как раз вести себя так, чтобы он не спешил генерить события, а дожидался окончания прогона блока. Но насколько это корректный путь? И зачем режим без этой опции, если он работает вот так "странно".

 

Существуют ли какие-то приёмы борьбы с этим эффектом? Кто-нибудь и как часто с этим сталкивался? В общем, прошу высказываться.

 

P.S. Симулятор: квеста.

Share this post


Link to post
Share on other sites
1 час назад, andrew_b сказал:

Не использовать блокирующие присваивания в межпроцессных взаимодействиях?

Не вариант. Блокирующие присваивания как таковые - основной способ описания комбинационной логики. Описанный в стартовом посте эффект возникает не из-за них, а из-за применения паттерна:

 

always_comb begin
  x = <default_value>;
  if(<condition>) begin
    x = <target_value>;
   end
end     

и при условии, что реально <condition> является true на протяжении серии прогонов блока, т.е. когда значение x реально не меняется. Но оно меняется при вычислении, и это порождает событие, которое запускает блоки, где есть ссылки (использование) переменной x, хотя по сути этого делать не нужно, т.к. реально значение переменной не изменилась. Это "паразитное" изменение туда-сюда переменной и приводит к потенциальным проблемам (но сам способ - задавать значение по умолчанию в начале блока - это очень удобный и эффективный паттерн кодирования: не нужно по всему дереву условий вставлять ветки else, чтобы указать, что будет, если ни одно из условий не сработало. Тут и забыть-пропустить легко, а потом ловить латчи на синтезе).

 

И действительно, по логике работы симулятора оно так и есть. В реальности симулятор, конечно, на таком простом примере не лажается, но на более сложном (в рабочем проекте) я словил такое поведение. 

 

Вот и хочется разобраться, что к чему и как это грамотно и корректно обойти. Костыльно пофиксить это я могу и сейчас - например, достаточно завести ещё одну переменную того же типа и пропустить переменную блока через эту с помощью assign - это магическим образом "рвёт" "цепочку" взаимной зависимости. Но костыль. И хочется всё же понимать, что "под капотом".

 

Ведь действительно - логика работы симулятора тут в соответствии с тем, что описано в Стандарте, хотя там не уточняется, в какой момент генерировать Active Event в always_comb блоке:

  1. при любом изменении переменной внутри;
  2. при изменении выходного относительно входного значения. 

Второй вариант, оказывается, в некоторых симуляторах (ссылка в стартовом посте) можно принудительно активировать, что решает проблему. Мои эксперименты (трассировка событий в симе) наталкивают на мысль, что движок симулятора следит за взаимными переходами, и если они начинают повторяться без изменений выходных величин, он сам рвёт цикл. Но вот это не всегда срабатывает. Или я чего-то не понимаю. Поэтому обращаюсь к коллективному разуму. :)

Share this post


Link to post
Share on other sites

может я чего-то недопонял: вопрос в том, что a=0; if (true) a=1; не генерит последовательность 0-1 каждое исполнение?

ну это же бубльгум  дельта-цикл, то есть результат будет учитываться только после исполнения всего блока. 

что такое дельта-цикл, если что - раньше на эту тему в верилоговских гайдах много писали, сейчас вроде не пишут, но он никуда не исчез

 

Share this post


Link to post
Share on other sites
6 минут назад, yes сказал:

может я чего-то недопонял: вопрос в том, что a=0; if (true) a=1; не генерит последовательность 0-1 каждое исполнение?

Да, именно он. Однако этот код не приводит к зацикливанию. Что-то его рвёт.

Share this post


Link to post
Share on other sites
1 minute ago, dxp said:

Да, именно он. Однако этот код не приводит к зацикливанию. Что-то его рвёт.

а почему должно быть зацикливание? это все выполняется в одном кванте времени, поэтому нет изменения, если до этого кванта а=1. соответственно нет срабатывания для других блоков.

ну что-то типа var-ов в VHDL - что туда не напиши, оно мгновенно исполняется

------

в нцсиме есть кстати возможность развернуть таймлайн в дельтациклы - там дррррр и такая фигня вылазит - ну то есть время не растет, а последовательность событий показана

Share this post


Link to post
Share on other sites

Приветствую!

5 minutes ago, yes said:

может я чего-то недопонял: вопрос в том, что a=0; if (true) a=1; не генерит последовательность 0-1 каждое исполнение?

Между такими присвоениями еще что то должно быть чтобы  сформировался дельта цикл и соответственно новые event, например слегка модифицированный код 

Spoiler

bit  a,a_q;
bit  b,b_q;

int aa,bb;

function void print_aa ();
  $display($sformatf("[%t] : aa: %4d", $time(), aa++));
endfunction

function void print_bb ();
  $display($sformatf("[%t] : bb: %4d", $time(), bb++));
endfunction

// always @(a)   $display($sformatf("[%t] : aa: %4d", $time(), aa++));
// always @(b)   $display($sformatf("[%t] : bb: %4d", $time(), bb++));

always_comb begin
  a = 0;

  if (trig)
    a = 1;

  // $display($sformatf("[%t] : aa: %4d", $time(), aa++));
  // `uvm_info("AA", $sformatf(">>aa: %4d", aa), UVM_HIGH)
  // print_aa();

  if (b)
    b_q = b;
end

always_comb begin
  b = ~a;

  // $display($sformatf("[%t] : bb: %4d", $time(), bb++));
  // `uvm_info("BB", $sformatf(">>bb: %4d", bb), UVM_HIGH)
  // print_bb();

  b = a;

end

 

Зацикливается только  при использовании  версии `uvm_info вывода на экран. Хотя там внутри вроде цепочка все таких же вызовов функций оканчивающихся $display

Удачи! Rob.

Share this post


Link to post
Share on other sites

там может #0 где-то внутри макросов?

 

в моих объяснениях следует читать, что это все происходит в _одном_ дельтацикле, то есть следующий не создается если внутри блока нет каких-то временных конструкций.

 

Share this post


Link to post
Share on other sites

Приветствую!

1 minute ago, yes said:

там может #0 где-то внутри макросов?

 

Нет. Там просто набор функций подсчета  статистики по типам  сообщений и инстансам,  и красивое форматирование. Все на функциях  (это даже не совсем честный `uvm_info  а собственная адаптация  для работы с не uvm симуляторами). Но есть блокирующие присвоения статистическим переменным в классе. 

 

Удачи! Rob.

Share this post


Link to post
Share on other sites
1 hour ago, dxp said:

always_comb begin
  x = <default_value>;
  if(<condition>) begin
    x = <target_value>;
   end
end

Я может не настолько опытен, но что значит Default value в комбинаторике? Не бывает значений по-умолчанию для схем, которые в первый попмент времени должны проанализировать входы и выдать выходные значения. И делать так по каждому изменению входных значений.

Попробуйте убрать такие присваивания, чтобы синтезатор не бокопорил.

Ну или обьясните мне как могут быть значения по-умолчанию для комбинаторики...

Share this post


Link to post
Share on other sites

Приветствую!

50 minutes ago, Nick_K said:

Я может не настолько опытен, но что значит Default value в комбинаторике? Не бывает значений по-умолчанию для схем, которые в первый попмент времени должны проанализировать входы и выдать выходные значения. И делать так по каждому изменению входных значений.

...

Попробуйте убрать такие присваивания, чтобы синтезатор не бокопорил.

Синтезатор как раз тут все нормально понимает, фактически это скрытая  ветка else для if.  Ну и такой стиль написания удобен и позволяет избежать многоэтажных  if .. else и нежелательных latch. Приведенные ниже записи для синтезатора  эквивалентны. 

always_comb begin
  x = <default_value>;
  if(<condition>) begin
    x = <target_value>;
   end
end

always_comb begin
  if(<condition>) begin
    x = <target_value>;
  end
  else begin
    x = <default_value>;
  end
end

     
always_comb begin
  x =  (<condition>) ? <target_value> : <default_value>;
end     

А вот для симуляции   это немного разные блоки кода - в первом присвоение переменной x  происходит 2 раза (хоть и с 0 задержкой во времени),  а во втором и третьем только однажды. Соответственно при определенных условиях в симуляторе это может создавать проблемы. 

 

Удачи! Rob.  

Share this post


Link to post
Share on other sites
1 hour ago, RobFPGA said:

Синтезатор как раз тут все нормально понимает, фактически это скрытая  ветка else для if.

Теперь понятно. Интересный подход, конечно. Я просто поле переключения на разработку ASIC, даже FF инициализировать боюсь - тут такие схемы насинтезирует (и то если не подвиснет), что чёрт голову сломит.

В общем то симуляторы нормальные это (инициализации) поведение тоже "понимают"

Share this post


Link to post
Share on other sites

Я уже поднимал однажды этот вопрос, и привёл вариант решения, к сожалению не самый удобный - создавать локальную копию проблемной глобальной переменной, и в конце блока переносить её в глобальную переменную.

Share this post


Link to post
Share on other sites
5 часов назад, Nick_K сказал:

Ну или обьясните мне как могут быть значения по-умолчанию для комбинаторики...

Для синтеза это состояние мультиплексора для всех случаев, помимо описанных. Удобно, чтобы их прямо не описывать, но для ясности можно перейти на else.

 

Share this post


Link to post
Share on other sites
10 часов назад, Timmy сказал:

Я уже поднимал однажды этот вопрос, и привёл вариант решения, к сожалению не самый удобный - создавать локальную копию проблемной глобальной переменной, и в конце блока переносить её в глобальную переменную.

Не видел вашего сообщения. Решение понятно - экранировать изменения внутри блока. Это лучше, чем прокидывать значение через ещё одну переменную: костыль получается локальный (не покидает пределов блока). Если ничего лучше не придумается, возьму на вооружение, спасибо!

 

@all.

 

Тем не менее, ситуация не такая простая. Например, код из стартового поста не зацикливается, а по логике должен! Но нет!! Вопрос: почему?

Более того, в ситуации, когда зацикливается, если вставить прокидывание значения одной из переменных через ещё одну через assign, то зацикливание тоже прекращается, хотя цепочка событий не ломается - значение всё так же меняется туда-сюда и потом передаётся в другой блок, просто через промежуточный assign. И этого достаточно, чтобы порвать зацикл. Почему?

 

С ситуацией вендоры симуляторов явно знакомы, иначе откуда бы у ncsim взялся ключ -delay_trigger? Если так и проблема явная и понятная, то почему не сделано так, чтобы на уровне логики работы ядра симулятора пофиксить это? Ведь это же достаточно несложно - достаточно генерить события по финальному результату - в конце блока определять, изменилась ли переменная или нет. Именно это делает ключ -delay_trigger и именно это делает вариант, предложенный @Timmy. Так почему это не сделать нормой?

 

Мой вариант ответа на эти вопросы: да, разработчики симуляторов знают об этом эффекте (и наверняка получше всех) и они наверняка применяют какие-то приёмы, которые в большинстве случаев эффективно рвут такие паразитные зацикливания - к такому выводу пришёл по результату трассировки событий в примере из стартового поста - там при проходе по шагам видно, что сим прыгает пару раз между блоками - понятно, евенты его форсят на это, но после второго раза почему-то снова на цикл не идёт - возможно, есть какой-то монитор внутри, который подсчитывает циклы, в которых и между которыми ничего не меняется, и после пары итераций блокирует цепочку. В более сложных случаях он иногда не справляется и пропускает этот клинч. Как случилось у меня в рабочем проекте. 

 

В общем, отказываться от такого удобного и эффективного паттерна кодирования не хочу, но остаётся неприятный осадок - теперь при написании такого кода буду подспудно ожидать факапа на симе. :(

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.