Ещё раз о задержках в исходном коде проекта FPGA или простой вопрос для собеседования на вакансию разработчика FPGA



    Некоторое время назад при обсуждении в компании профессиональных разработчиков FPGA возникла дискуссия о прохождении собеседования. Какие вопросы там задают, и что можно было бы задать. Я предложил два вопроса:

    1. Приведите пример синхронного кода без использования задержек, который даст разные результаты при моделировании и при работе в реальной аппаратуре
    2. Исправьте этот код при помощи задержек.

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

    Я уже немного касался этого вопроса в предыдущей статье. Сейчас более подробно. Вот текст примера:

    library IEEE;
    use IEEE.STD_LOGIC_1164.all;
    
    entity delta_delay is
    end delta_delay;
    
    architecture delta_delay of delta_delay is
    
    signal	clk1		: std_logic:='0';
    signal	clk2		: std_logic;
    alias	clk3		: std_logic is clk1;	-- назначение другого имени clk1
    
    signal	a			: std_logic;
    signal	b			: std_logic;
    signal	c			: std_logic;
    signal	d			: std_logic;
    begin							
    --- Формирование тестовых сигналов ---
    clk1 <= not clk1 after 5 ns;
    
    pr_a: process begin
    	a <= '0' after 1 ns;
    	wait until rising_edge( clk1 );
    	wait until rising_edge( clk1 );
    	a <= '1' after 1 ns;
    	wait until rising_edge( clk1 );
    	wait until rising_edge( clk1 );
    	wait until rising_edge( clk1 );
    	wait until rising_edge( clk1 );
    end process;	
    	
    --- Синтезируемая часть - переназначение тактового сигнала  ---
    clk2 <= clk1; -- вот в этом проблема, не надо так делать без крайней необходимости
    
    --- Вариант 1 - Синтезируемая часть без задержек  ---
    
    b <= a when rising_edge( clk1 );
    c <= b when rising_edge( clk1 );
    d <= b when rising_edge( clk2 );
    
    --- Вариант 2 - Синтезируемая часть с задержками  ---
    --
    --clk2 <= clk1;
    --b <= a after 1 ns when rising_edge( clk1 );
    --c <= b after 1 ns when rising_edge( clk1 );
    --d <= b after 1 ns when rising_edge( clk2 );
    
    --- Вариант 3 - Синтезируемая часть без задержек но с переназначением сигнала через alias  ---
    --b <= a when rising_edge( clk1 );
    --c <= b when rising_edge( clk1 );
    --d <= b when rising_edge( clk3 );
    
    end delta_delay;
    

    Для упрощения весь код размещён в одном компоненте.

    Сигналы clk1 и a это сигналы тестового воздействия. clk1 это тактовая частота 100 MHz, Сигнал а держится два такта в 0 и четыре такта в 1. Сигнал a формируется с задержкой 1 nc относительно нарастающего фронта clk1. Этих двух сигналов достаточно для описания проблемы.

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

    Вот результаты моделирования для варианта 1:



    На диаграмме визуально видно, что сигналы тактовой частоты clk1 и clk2 совпадают, но на самом деле clk2 задержан относительно clk1 на величину дельта задержки. Сигнал c отстаёт от сигнала b на один такт. Это правильно. Но вот сигнал d должен совпадать с сигналом c, а этого не происходит. Он срабатывает раньше.

    Давайте вспомним что такое дельта задержка. Это принципиальное понятие, на нём основана работа событийных симуляторов, которыми мы пользуемся при моделировании логических схем.

    Симулятор имеет понятие модельного времени. К этому модельному времени привязаны все события в системе. Давайте посмотрим на формирование тактовой частоты:

    clk1 <= not clk1 after 5 ns;

    Предположим что сейчас мы моделируем только clk1, других сигналов нет.
    В начальный момент времени clk1 равен 0, это задано при объявлении сигнала. Симулятор видит требование инвертировать сигнал. Ключевое слово after даёт инструкцию провести назначение нового значение через 5 ns относительно текущего модельного времени. Симулятор это видит и делает отметку, что в момент времени 5 ns значение clk1 будет равно 1. Пока это модельное будущее, оно кстати ещё может измениться. Далее симулятор просматривает остальные сигналы. Симулятор увидит что для данного момента модельного времени всё выполнено и он может рассчитывать следующий момент. Возникает вопрос – а какой момент следующий? В принципе возможны разные варианты. Например Simulink имеет режим с фиксированным шагом. В этом случае произойдёт приращении модельного времени на какую то величину и вычисления продолжаться.

    Системы моделирования цифровых схем делают по другому. Они переходят к ближайшему событию, которые они уже разместили в будущем на своей оси модельного времени. В данном случае это будет момент 5 нс. Симулятор увидит что clk1 изменился и просчитает для него новое значение, это будет 0 который также будет размещён с задержкой в 5 нс на временной оси. Т.е. это будет момент 10 нс. И так процесс будет продолжаться пока не закончиться заданное время моделирования.

    Теперь давайте добавим сигналы a и b.

    Сигнал a назначается в процессе. Для сигнала b используется условная конструкция when; Функция rising_edge(clk1) анализирует clk1 и возвращает true когда зафиксирован фронт, т.е. предыдущее значение равно 0 а текущее равно 1.

    В момент модельного времени 5 ns произойдёт изменение clk1. Он станет равным 1 и для момента 10 ns будет создано событие установки его в 0. Но это потом. Пока мы ещё в моменте 5 ns и продолжаем вычисления. Симулятор переходит к строчке
    b<=a when rising_edge(clk1);
    Поскольку есть функция которая зависит от clk1 то симулятор вычислит значение функции, увидит что она вернула true и произведёт присваивание
    b<=a;


    Вот здесь начинается самое интересное — когда надо изменить значение b. Казалось бы надо изменить его сейчас, в этот момент времени. Но у нас параллельные процессы. Может быть, нам ещё понадобиться значение b для расчёта других сигналов. И вот здесь появляется понятие дельта задержки. Это минимальная величина, на которую смещается модельное время. Эта величина даже не имеет размерности времени. Это просто дельта. Но их может быть много. Причём настолько много что симулятор просто останавливается по ошибке или зависает.
    Итак, новое значение b будет установлено для момента 5 ns + 1 (1 – это первая дельта задержка). Симулятор увидит, что рассчитывать для момента 5 ns уже нечего и перейдёт к следующему моменту, а это будет 5 ns + 1; В этот момент rising_edge(ckl1) не срабатывает. А значение b будет установлено в 1. После этого симулятор перейдёт к моменту 10 nc.

    А вот теперь давайте добавим сигналы c, d и разберёмся почему они разные.
    Лучше всего это рассмотреть момент модельного времени 25 ns с учётом дельта задержек

    delta clk1 clk2 re(clk1) re(clk2) b c d
    0 1 0 true false 0 0 0
    1 1 1 false true 1 0 0
    2 1 0 false false 1 0 1

    Примечание: re — rising_edge

    Из таблицы видно что в момент срабатывания функции rising_edge(clk2) значение b уже равно 1. И поэтому оно будет присвоено сигналу d.

    Исходя из здравого смысла это не то поведение, которое мы ожидали от кода. Ведь мы просто переназначили сигнал clk1 на clk2 и ожидали, что сигналы c и d будут одинаковыми. Но следуя логике работы симулятора это не так. Это ПРИНЦИПИАЛЬНАЯ особенность. Эту особенность конечно надо знать разработчикам FPGA проектов и поэтому это хороший и нужный вопрос для собеседования.

    Что же произойдет при синтезе? А вот синтезатор проследует здравому смыслу, он сделает сигналы clk2 и clk1 одним сигналом и поэтому c и d тоже будут одинаковыми. А при определённых настройках синтезатора они тоже будут объединены в один сигнал.

    Это как раз случай, когда моделирование и работа в реальной аппаратуре приведут к разным результатам. Хочу обратить внимание, что причина разных результатов – это разная логика симулятора и синтезатора. Это ПРИНЦИПИАЛЬНАЯ разница. Это не имеет ничего общего с временными ограничениями. И если ваш проект в модели и в железе показывает разные результаты то проверьте, может быть там закралась конструкция подобная

    clk2 <= clk1 

    Теперь второй вопрос – исправьте этот код при помощи задержек.
    Это вариант 2. Его можно раскомментировать и промоделировать.
    Вот результат.



    Результат правильный. Что же произошло? Давайте ещё раз составим таблицу для интервала 25 – 36 нс
    time delta clk1 clk2 re(clk1) re(clk2) b c d
    25 0 1 0 true false 0 0 0
    25 1 1 1 false true 0 0 0
    26 0 1 1 false false 1 0 0
    35 0 1 0 true false 1 0 0
    35 1 1 1 false true 1 0 0
    36 0 1 1 false false 1 1 1

    Видно, что значение b не меняется в моменты фронтов clk1, clk2. Задержка в 1 нс уводит момент изменения сигналов за зону срабатывания фронтов. Этот код становиться ближе к реальности. В реальной схеме существует какое то время на срабатывание триггера и на распространение сигнала. Это время должно быть меньше периода тактовой частоты, собственного говоря, именно этого добивается трассировщик и именно это проверяет временной анализ.

    Причина возникновения ошибки это переназначение тактового сигнала обычным присваиванием при котором появляется дельта задержка. Однако язык VHDL имеет конструкцию alias. Это позволяет получить другое имя для сигнала. Вот объявление:

    alias clk3 	: std_logic is clk1;

    В тексте примера можно раскомментировать вариант 3 – он будет работать правильно.

    Данный пример написан на языке VHDL. Может быть это проблемы только этого языка? Но вот те же варианты на языке Verilog.

    Скрытый текст
    `timescale 1 ns / 1 ps
    
    module delta_delay_2 ();
    	
    reg  clk1 = 1'b0;	 
    reg  clk2;
    wire clk3;		
    
    reg a = 1'b0;
    reg b;
    reg c;
    reg d;
    
    initial begin
    forever clk1 = #5 ~clk1;
    end 
    
    initial begin
    repeat(10)
    begin
    
    #20 a = 1'b1;
    #60 a = 1'b0;
    end
    end
    
    // Синтезируемая часть - переназначение тактового сигнала  ---
    always @(clk1) clk2 <= clk1;	
    	
    // Вариант 1 - Синтезируемая часть без задержек  
    	
    always 	@(posedge clk2)	d <= b;			
    	
    always 	@(posedge clk1)
    begin	
    	c <= b;		
    	b <= a;
    end				  
    
    // Вариант 2 - Синтезируемая часть с задержеками  
        
    //always 	@(posedge clk1)	b = #1 a;	
    //	
    //always 	@(posedge clk1)	c = #1 b;		
    //	
    //always 	@(posedge clk2)	d = #1 b;			
    	
    // Вариант 3 - Синтезируемая часть без задержек 
    // но с переназначением сигнала через assign  
    
    //assign clk3 = clk1;		
    //
    //always 	@(posedge clk3)	d <= b;			
    //	
    //always 	@(posedge clk1)
    //begin	
    //	c <= b;		
    //	b <= a;
    //end	
    endmodule
    



    • Вариант 1 – без задержек. Работает неправильно.
    • Вариант 2 – с задержками. Работает правильно.
    • Вариант 3 – переназначение через wire. Работает правильно.

    В языке Verilog есть понятие reg и wire. В данном случае переназначение тактового сигнала через wire выглядит более естественным. Это является аналогом присвоения через alias в языке VHDL. Это несколько снимает напряжённость проблемы, но всё равно это надо знать.
    Также в языке Verilog есть понятие блокирующего и неблокирующего присваивания. Назначение сигналов b и c можно написать и по другому:

    always 	@(posedge clk1)
    begin	
    	c = b;		
    	b = a;
    end	
    

    А можно так:

    always 	@(posedge clk1)
    begin	
    	b = a;
    	c = b;		
    end
    

    В зависимости от порядка строк результат будет разный.

    Возвращаясь к теме собеседования хочу ещё раз подчеркнуть что эти вопросы на понимание сути проблемы. А уже из понимания проблемы можно делать разные выводы, например какой использовать стиль кода. Лично я всегда использую назначение задержек.

    Файлы примеров доступны здесь

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Вы используете задержки в исходном коде VHDL или Verilog?
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 49
      0
      А какие сейчас симуляторы используют? Они платные?
        0
        Я использую Active-HDL и Vivado. Active-HDL платный. Vivado имеет бесплатную версию.
          0
          Icarus verilog + gtkwave. Правда с тем проектом который делаю сейчас, хочу перейти на verilator. Уж больно там тесты громадные. Всё это бесплатно и опенсорсно.
          0
          Не понял, почему тему заминусовали? Конечно неблокирующее присваивание в операциях по клоку это говнокод, тут спору нет. Но знать такие вещи нормальный разработчик по-моему просто обязан. Проглядел пока весьма бегло (отвлёкся немного от работы), но статья по-моему вполне достойная.
            0
            Прошу прощения, не могли бы Вы всё-таки пояснить, из каких соображений используете именно задержки? И кстати интересно, как на них реагируют синтезаторы? Тут пока проголосовали трое (и я в том числе). Результат — 100% тех кто задержки не использует.
              0
              В соответствии со стандартом языка синтезатор задержки игнорирует.
              Когда то много лет назад я потратил очень много времени на поиск несоответствия между результатами моделирования и реальной работой. Причина — вот такое присваивание клока. Обнаружить это в чужом коде практически нереально. А в наших проектах чужого или старого кода очень много. Установка задержек проблему решает. Ну и кроме того на временной диаграмме сигналы с задержками выглядят гораздо понятней.
                +1
                Честно говоря присваивания клока вообще никогда не видал. Правда практически никогда не работал с унаследованным кодом на верилоге. Почти всегда писал своё и в своём стиле. Надо будет подробнее разобраться в Вашей статье и примерах. Во всяком случае большое спасибо за публикацию. Не каждый день удаётся взглянуть на знакомый предмет под несколько иным углом. И это всегда интересно.
              +6
              «Придумайте грабли которые попадут вам точно по тестикулам. Придумайте как их исправить.»
              Крайне надуманная проблема, в т.ч. как вопрос для собеседования. Ну т.е. если вы только что искали такую проблему в коде и живете этим — то вам очевидно. Человек же с улицы не факт что вспомнит об этом по наводящим вопросам. Когда же вы ткнете его носом в этот код, недоуменно посмотрит на вас: «Ну да, есть такой нюанс. Но зачем кто-то так делает?»
              PS Послезавтра кто-нибудь напишет «clk2 <= clk1 after 5 ns;» тоже не знаю зачем и всю эту эпопею можно начинать сначала.
                0
                Придумайте грабли которые попадут вам точно по тестикулам. Придумайте как их исправить.

                Иногда такая постановка вопроса вполне себе имеет смысл. Хотя бы для разминки мозгов.
                +3
                Я использую задержки только в тестбенчах. В боевом коде — никогда.
                  +2
                  Аналогично.
                  +3
                  За свою практику я использовал задержки только в верхнем уровне тестовой сборки из fpga и памяти (имитировал clock to output и input setup у fpga). Исключительно ради нормального функционирования rtl-кода fpga и модели памяти, которую поставляет производитель и которая напичкана контролем таймингов. Ну и в тестах. Хотя и в них предпочитаю тестовое воздействие с регистра подавать, который по тактовому сигналу изменяется.
                  По поводу использования задержек внутри боевого кода активно согласен с предыдущими ораторами — это отличный способ выстрелить себе в ногу. Логично проверить на собеседовании, что кандидат в курсе, что это плохо-плохо (и что за переназначение тактового сигнала путём присваивания отрывают руки). А вот придумывать тестовые задачи на это плохо-плохо попахивает глупостью.
                    0
                    По поводу использования задержек внутри боевого кода активно согласен с предыдущими ораторами — это отличный способ выстрелить себе в ногу.

                    А собственно говоря почему? Наличие задержек делает временную диаграмму более наглядной. Гарантирует, что не будет проблемы при подобном переприсваивании клока.
                    Синтезатор задержки всегда игнорирует.
                      0
                      Переприсваивание клоков не может быть корректно синтезировано. Внесение такой конструкции в код — грубая ошибка. Маскирование грубой ошибки на этапе моделирования путём использования задержек никаким образом не поспособствует корректному синтезу, а только затруднит поиск этой самой грубой ошибки.
                      Неужели это так трудно понять? Все эти «примеры» эквивалентны элегантному способу выстрелить себе в печень левой рукой стоя на голове. И обсуждения формы костыля, который позволяет при выстреле точно попасть в нужную долю этой печени.
                      В реальной жизни, где нужно написать rtl-код, который исполняется в железе одинаково с симулятором, всё это не нужно.
                        0
                        Переприсваивание клоков не может быть корректно синтезировано.

                        Это не так. Синтезатор прекрасно понимает эту конструкцию и делает эту цепь общей.
                          +1
                          Вы сейчас ведёте речь именно про переприсвоение, а не про assign (в терминологии verilog-а)? Если да, то вы глубоко заблуждаетесь. В общем то это ваше личное дело, но мне банально жаль тех, кого вы научите тому, что «синтезатор понимает эту конструкцию».
                          P.S. Не думаю, что имеет смысл продолжать это бесплодное обсуждение с вами.
                            0

                            Я говорю о присвоении сигнала в VHDL:
                            clk1 <= clk2;
                            Это синтезируется.

                    –4
                    Если честно, немного удивлён результатами голосования. Лично я всегда использую задержки при неблокирующем присваивании.

                    Неблокирующее присваивание без задержки выполняется в симуляторе мгновенно, хотя в реальности это триггер и новое значение на его выходе формально образуется через время tCQ (clock to q delay). Без этой задержки события в симуляторе будут происходить на такт раньше, что может привести к неправильному пониманию работы схемы при симуляции. В то время как с задержкой мы видим то, что происходит в реальности. Собственно исходя из этих соображений можно привести массу примеров того, когда результаты симуляции отличаются от железа.

                    Так уж получилось, что на широких просторах интернета мне на глаза попадались исходники фирменных ядер Xilinx, Altera, Mictrotronix, SLS и т.д. Очень часто вижу задержки при неблокирующем присваивании. У Xilinx вообще все ядра симулируются с задержкой на триггерах в 100ps.
                      +2
                      1. Никак не могут «события в симуляторе происходить на такт раньше» если тест написан правильно. Если неправильно — задержки не спасут.
                      2. Мы не видим что происходит в реальности, т.к. в реальности задержки куда как многообразнее и с ними справляется трассировщик на пару с временным анализатором
                      3. Задержки ухудшают читаемость кода, если вы пишете не триггер, а что-то более сложное.
                      4. У меня сейчас нет доступа к рабочей машине, чтобы посмотреть исходники, но не замечал особого обилия задержек кроме как в коде предназначенном для timing simulation. Тот же github можно взять для примера.
                        +1
                        Неблокирующее присваивание без задержки выполняется в симуляторе мгновенно

                        Погуглите по словам «delta delay».
                        Без этой задержки события в симуляторе будут происходить на такт раньше, что может привести к неправильному пониманию работы схемы при симуляции.

                        Если вам не сложно, приведите пример этого чудесного явления. Я ещё не встречался с таким явлением в коде, который написан с соблюдением стандарта.
                        У Xilinx вообще все ядра симулируются с задержкой на триггерах в 100ps.

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

                        В синхронных схемах (т.е. в 99% случаев для проектов для FPGA), результаты железа будут отличаться от реальности только при нарушении таимингов и/или при некорректно заданных таймингах для специфичных случаев межклокового взаимодействия и сигналов с многотактовым таймингом.
                          0
                          Если вам не сложно, приведите пример этого чудесного явления. Я ещё не встречался с таким явлением в коде, который написан с соблюдением стандарта.

                          Пример в этой статье. Вот фрагмент кода:
                          always 	@(posedge clk1)
                          begin	
                          	c = b;		
                          	b = a;
                          end
                          

                          Если в нём поменять местами строки с присваиванием, то результат будет разный. Это опять же связано с отсутствием дельта задержки.
                          В синхронных схемах (т.е. в 99% случаев для проектов для FPGA), результаты железа будут отличаться от реальности только при нарушении таимингов и/или при некорректно заданных таймингах для специфичных случаев межклокового взаимодействия и сигналов с многотактовым таймингом.

                          Не так. Один из примеров — в данной статье.

                            0
                            Если в нём поменять местами строки с присваиванием, то результат будет разный.

                            Разумеется он будет разный, в полном соответствии со стандартом языка.
                            Не так.

                            Высосанный из пальца пример, как не надо делать ни в коем случае, как то опровергает моё утверждение, что в 99% случаях rtl-кода для FPGA так делать не надо?
                              0
                              Это опять же связано с отсутствием дельта задержки.

                              Неверно. Это связано с тем, что неблокирующие присваивания как раз сделаны для того, чтобы можно было сразу забрать результат и использовать его в следующем присваивании, но нужно следить за порядком выполнения операций. Вместо того чтобы использовать правильный инструмент (блокирующее присваивание), вы пытаетесь его симулировать с помощью задержек.
                                +1
                                NetNazgul, похоже, у вас ошибка в терминологии.

                                Блокирующее присваивание — это оператор "="
                                Неблокирующее присваивание — оператор "<="

                                Описано в пунктах 10.4.1 и 10.4.2 стандарта на SystemVerilog от 2012 года.
                            0
                            Неблокирующее присваивание без задержки выполняется в симуляторе мгновенно

                            Да нет, симулятор как раз их вносит. Почитайте у Полякова (названия сейчас увы не помню, книжка про верилог и vhdl). Там это хорошо объясняется на примере RS-триггера из двух элементов И-НЕ.

                            Лично я всегда использую задержки при неблокирующем присваивании

                            Если Вас не затруднит, не могли бы Вы выложить куда-нибудь образцы кода? Тема мне кажется вполне достойная.
                              +1
                              Чтобы не повторяться с ответами, nerudo, old_bear, eugenk, предлагаю вам прочитать эту небольшую статью, она даёт исчерпывающий ответ на вопрос о целесообразности использования задержек при неблокирующем присваивании.
                                +1
                                В этой статье речь про то, как её автор пытается смешать ежа RTL и ужа gate level-а из-за своих нетрадиционных взглядов на дизайн ASIC-ов. Какое отношение она имеет к обсуждаемому вопросу написания корректно синтезируемого кода для FPGA?
                                  +2
                                  Хорошо, давайте уберём в сторону gate level симуляцию, хотя это вполне себе аргумент, мы же не коня в вакууме описываем, а работаем в конечном итоге с реальной ПЛИС и её примитивами. Я не защищаю автора статьи с проблемами переприсвоения тактового сигнала. Но задержка при неблокирующем присваивании просто напросто упрощает понимание диаграмм. Факт в том, что по активному фронту на триггере образуются новые данные, но в реальности это не так, новые данные появляются позже. Это задержка может быть больше или меньше, но она строго больше нуля и меньше периода (желательно). Хорошо, когда эта задержка в реальности меньше периода с учётом всех накладных расходов (logic delay + routing delay), это означает, что мы уложились по таймингам.
                                    0
                                    Вся модель симуляции с дельта-задержками была придумана для того, чтобы избежать этих велосипедов с добавлением задержек в каждом операторе.
                                    Вы улучшаете читаемость диаграмм (якобы; лично у меня нет проблем с чтением их без задержек), ухудшаете читаемость кода, особенно для VHDL, который и так несколько перегружен. Потом какой-нибудь джуниор начнет писать
                                    #1 a<=b вместо a<= #1 b; и станет совсем интересно.
                                    Ну и могу еще раз отметить — задержки в аппаратуре куда как многообразнее. Понятие клокового дерева в последних микросхемах FPGA крайне размыто. И два соседних в коде триггера могут находиться в разных клоковых регионах, тактируясь разными физическими сигналами. Какие там окажутся задержки и фактический hold-time для триггера, который вы обставляете этими задержками — одному богу известно.
                                      0
                                      Вы как раз описали механизм delta delay, который уже есть в языке и без которого в принципе симуляция не будет функционировать.
                                      Уложились вы по таймингам или нет, вам сообщает софт производителя fpga. К симуляции работы синхронной логики это не имеет никакого отношения. Всё что от вас требуется на этапе RTL-кода — ставить не больше асинхронной логики между двумя регистрами, чем может уложиться в нужный тайминг. Это «не больше» достигается проверочной сборкой проекта и опытом. Со временем первая составляющая уменьшается в пользу второй.
                                      Вы же просто смешиваете разные уровни и задачи FPGA-дизайна, якобы для наглядности и упрощения процесса. А на практике такая путаница ожидаемо приводит к ошибкам, т.к. вместо того, чтобы всегда следовать простому правилу «регистр -> регистр асинхронная логика -> регистр» и забыть про всё лишнее кроме логики, вам нужно постоянно следить, чтобы вас не накрыло вашим велосипедом с пачкой костылей на багажнике.
                                        0
                                        *«регистр -> асинхронная логика -> регистр"
                                          0
                                          На самом деле вы всё верно говорите, и я с вами в общем плане согласен.

                                          Вы же просто смешиваете разные уровни и задачи FPGA-дизайна, якобы для наглядности и упрощения процесса.
                                          Почему я смешиваю уровни? Синтезатор игнорирует эти задержки. Возможно для вас будет аргументом то, что я хотел бы видеть диаграммы при симуляции так, как если бы к реальному железу был подключен реальный логический анализатор. Логический анализатор показал бы, что состояние на выходе триггера изменяется с задержкой, а не строго по активному фронту. И всё! Если мы не имеем ввиду gate level, то ни для какой другой задачи эта задержка не нужна. Ещё, наверное, стоит сказать, что правильнее эту задержку определять как «parameter», предоставляя пользователю право обнулять это значение.
                                            0
                                            Когда я последний раз пользовался логическим анализатором (не осциллографом) он, что характерно, показывал изменения сигналов именно по фронту того сигнала, который был выбран в качестве синхро-входа. :)
                                            Как именно вам легче воспринимать диаграммы — это индивидуальный вопрос и я не претендую вам в нём указывать. Но беда в том, что использование задержек для придания некой красивости, может вылиться в маскирование ошибок в коде.
                                –1
                                Хочу ещё раз отметить. Я занимаюсь реальной работой по проектированию ПЛИС в течении длительного времени. В статье приведён простой пример который демонстрирует РЕАЛЬНУЮ проблему. Я охотно верю, что если один разработчик делает один проект то проект может быть идеальным. У нас не так. У нас много проектов и каждом до 90% чужого кода. Отследить что бы не было переприсваивания клока практически невозможно. Однажды я очень сильно об это обжёгся, я очень много времени потратил на поиск подобной ошибки.
                                Так что утверждения что это надуманная проблема я считаю ошибочными, это РЕАЛЬНАЯ проблема.
                                Решения у этой конкретной проблемы может быть только два:
                                1. Вставляем задержки
                                2. Делаем присваивание клока через alias или assign

                                Я выбрал первый способ. Второй я считаю ненедёжным. Ну а лучше всего использовать оба.
                                  +1
                                  У нас много проектов и каждом до 90% чужого кода. Отследить что бы не было переприсваивания клока практически невозможно.

                                  Вы простите, но это высказывание эквивалентно варианту «у нас много С-разработчиков пишут общий код и отследить, чтобы в коде не было undefined behaviour невозможно».
                                  Если тим-лид в софтовом проекте скажет такое руководству, то либо его нужно увольнять, либо срочно отправлять на курсы повышения квалификации в области управления проектами.
                                    –1
                                    Это уже уход в область управления проектами и организацией в целом.
                                    Вне зависимости от тимлида есть только два решения этой проблемы — задержки и правильное присваивание. Вопрос — что выбираем?

                                    P.S. Если тимлид утверждает что в коде совершенно нет «undefined behaviour» то это должно очень сильно насторожить руководство.
                                      +1
                                      Это уже уход в область управления проектами и организацией в целом.

                                      Совершенно с вами согласен — описываемая вами ситуация является прямым следствием грубых ошибок в управлении проектом. В других условиях такая ситуация возникнуть не может. Попытка замазать эти ошибки всяческими костылями — это ещё более грубая ошибка.
                                      Вне зависимости от тимлида есть только два решения этой проблемы — задержки и правильное присваивание. Вопрос — что выбираем?

                                      Для меня второй вариант является очевидным выбором. И я немного в шоке, что для кого-то, имеющего отношение к FPGA-дизайну, это не так.
                                      Если тимлид утверждает что в коде совершенно нет «undefined behaviour» то это должно очень сильно насторожить руководство.

                                      Не надо коверкать мои слова. Если тим-лид утверждает, что undefined behaviour — это нормальная составляющая в коде, вот тогда руководство должно не только сильно насторожиться но и сильно напрячься.
                                        –1

                                        Эта проблема совершенно не имеет отношения к управлению проектом. Это проблема знания матчасти. А именно знания случая когда моделирование и синтез даст разные результаты.
                                        Переприсваивание клока не является ошибкой, тем более грубой. Синтезатор это корректно обрабатывает, код будет работать. Симулятор также всё обработает, результат будет, но немного другой. Но как говориться — что просили, то и получили.


                                        Я кстати не первый раз публично поднимаю тему с задержками и всегда получаю волну критики. Основной аргумент — лень добавить несколько слов. А то что это позволяет решить реальные проблемы и в итоге сэкономить кучу времени как то упускается из виду. Здесь действует принцип — раз я с этим не сталкивался значит этого нет. Ну а я сталкивался.

                                          +1
                                          Ок, я попробую один последний раз.
                                          Напишите мне пожалуйста, эквивалентом какого аппаратного элемента FPGA является переприсвоение тактового сигнала в RTL-коде. На всякий случай отмечу, что мы не говорим сейчас о gate level-е и ASIC-ах, а именно про RTL-код и FPGA.
                                          А если вы не затруднитесь ссылкой на coding rules от любого из производителей FPGA, где подтверждается, что переприсовение тактового сигнала — это допустимая
                                          конструкция для их синтезатора и рассказывается во что она будет гарантировано синтезирована, то будет совсем чудесно.
                                          Заранее спасибо.
                                            –1

                                            Для определённости — я говорю о Xilinx и синоезаторах из пакетов ISE и Vivado.
                                            Конструкция VHDL: clk1 <= clk2; будет реализована одной цепью. Цепь может получить имя одного из сигналов, но скорее всего будет называться по имени источника.
                                            Я сейчас не могу привести ссылку на кодестайл, не уверен что это вообще есть в описаниях. Но реально это работает.

                                              +1
                                              Вы опять отвечаете не на тот вопрос, который я вам конкретно задал. Потому что на мой вопрос вы ответить не можете.
                                              А повторять как мантру «это проходит в синтезаторе Х» ничем не отличается от варианта «этот undefined behaviour приводит к нужному нам поведению в компиляторе Х версии Y и поэтому мы его будем использовать с коде».
                                              За сим я откланиваюсь. Не вижу смысла продолжать дискуссию, по крайней мере до тех пор, пока вы не приведёте более весомые аргументы, чем «у меня это проходит» и «я много лет так делаю».
                                                –1

                                                Вы задали вопрос — эквивалентом какого аппаратного элемента FPGA является переприсвоение тактового сигнала.
                                                Я отвечаю, причём уже много раз, это будет одна и та же цепь.
                                                Разве я не понятно ответил?
                                                Или вы не понимаете проблему?

                                                  +1
                                                  Нет, эквивалентом одной и той же цепи является assign/alias. Корректного эквивалента элемента, который задерживает тактовый сигнал на величину delta delay в рамках синтеза RTL-кода не существует. Если синтезатор не посылает вас трёхэтажно при наличии такой конструкции в коде, то это всего лишь недоработка синтезатора. Если вы считаете, что правильность вашего кода определяется не стандартом языка, а поведением конкретного синтезатора, значит у вас большие проблемы как у разработчика.
                                                  Обсуждать это дальше я смысла не вижу. Всего вам наилучшего.
                                                    –1

                                                    А ничего что я про VHDL пишу?

                                                      –1

                                                      Вы конечно можете не отвечать, я не навязываюсь. Но хочу ещё раз отметить, синтезатор задержки ИГНОРИРУЕТ. Причём все, включая дельта задержку.
                                                      Поэтому любое присваивание, что через assign/alias, что простое присваивание сигнала в vhdl приведёт к одному результату. Это будет одна цепь, без всяких мифических элементов задержки на величину delta. Это кстати тоже необходимое знание для разработчика FPGA.
                                                      На мой взгляд это просто и очевидно. Неужели я так плохо объясняю?

                                    +5
                                    Не использую задержки в исходном коде.
                                    Естественно, за исключением случаев, когда они необходимы (например, в моделях внешней памяти и т.п.).

                                    Согласен с приведёнными выше аргументами про «выстрел в ногу» и маскирование ошибки.
                                    Поэтому повторяться не буду.

                                    Дополню только следующими пунктами:
                                    1. Для синтезируемого подмножества Verilog есть стандарт IEEE Std. 1364.1 — 2002 «Standard for Verilog Register Transfer Level Synthesis».
                                      В этом стандарте, в Annex B, пункт B.12 Timing delays сказано:
                                      Synthesis tools ignore time delay in a model. Adding time delays to a Verilog pre-synthesis simulation can cause a mismatch between pre-synthesis and post-synthesis simulations and in general should be avoided.

                                    2. Есть ещё статья от всем известных Sunburst Design:
                                      www.sunburst-design.com/papers/CummingsSNUG2002Boston_NBAwithDelays.pdf

                                      Там авторы не настолько категорично настроены против использования задержек. В качестве доводов «за» они называют следующее:
                                      1. Более удобную отладку с временными диаграммами
                                      2. Решение некоторых проблем при смешанной RTL/Gate-level симуляции

                                      Доводов «против» также хватает.

                                      Мое мнение по первому пункту «за» — неудобство отладки по временным диаграммам без использования дополнительной задержки полностью уходит примерно через неделю практики. И для инженеров с серьёзным опытом это точно не должно быть причиной для использования задержек.
                                      По второму пункту — да, это так. Но в статье и комментариях разбирается не этот случай.


                                    Общий вывод по статье.
                                    Самое плохое, на мой взгляд, то, когда начинающих FPGA-разработчиков вместо понимания сути вещей (как именно работают симулятор и синтезатор, какие конструкции есть в языках и когда и как их нужно правильно применять) учат «хитростям», которые позволяют избегать проблем, если повезёт. И которые дают ложное ощущение безопасности.

                                    Именно такое впечатление у меня сложилось после прочтения материала.
                                    Поэтому поставил статье минус, хоть обычно я двумя руками «за» все статьи по тематике FPGA.

                                    Я уже говорил свою позицию лично Дмитрию (dsmv2014).
                                    Он взрослый адекватный инженер, поэтому нормально воспринял мою критику.
                                    Надеюсь, что он также не будет на меня в обиде за то, что я написал своё мнение в комментарии.
                                      0
                                      Раньше в некоторых проектах использовал задержки, потом перестал. Да, немного удобнее воспринимать диаграммы, но значительный overhead в виде «after sim_delay» в каждом присвоении того не стоит. Кроме того, появлялись еще какие-то неупомянутые ниже проблемы, о которых сейчас не вспомню.

                                      Чисто технически задержки тянут за собой как минимум две проблемы:
                                      1. Задержку можно забыть написать в каком-то присвоении, и это притащит за собой обратно все те же самые проблемы, от которых мы хотели избавиться задержками.
                                      2. Если добавлять задержки *везде*, то при достаточном количестве вложенной логики можно умудриться перескочить через такт, что тоже ни к чему хорошему не приведёт.

                                      С синхросигналами в HDL вообще нужно работать осторожно и внимательно, конкретно вот пример
                                      clk2 <= clk1;
                                      ведь ничего не даёт в схеме, а только создаёт проблему. Я бы еще понял
                                      clk2 <= not clk1;
                                      или какую-то другую логику, но в таком случае это уже модификация синхросигнала, и работа с такими вещами требуется соответствующая.

                                      Добавлю, что занимаюсь в основном разработкой под ASIC, но часто с верификацией на FPGA, так что знаком с обоими «мирами».
                                        +2
                                        Дмитрий, спасибо за статью.
                                        Я надеюсь, что Вы не будете в обиде за мнение о статье.

                                        Мой комментарий содержит две части, в каждой из них делается акцент на разных проблемах.

                                        Часть первая (содержание статьи)

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

                                        Главная проблема в том, что Вы не даете доказательств. Например, ссылки на стандарт, который обсуждается в статье. На мой взгляд очень важно делать отсылки на конкретные части конкретного документа, когда мы говорим про нюансы языка/окружения. Читатель должен иметь возможность перечитать это на английском языке и “перепроверить” перевод/понимание автора статьи. Мы бездумного сравниваем примеры VHDL и Verilog, но может там есть разница в симуляции? Было бы здорово указать марку и версию симулятора, в котором производились “измерения”. В качестве доказательства, что в железе будет по-другому можно было бы приложить скриншот из SignalTap/ChipScope.

                                        Дельта-задержка это не единственный “нюанс” в симуляции. Там есть “слоты”, “регионы”, “события” и так далее. Я ожидаю от статьи, где разбираются задержки и симуляция, описание как действительно работает симулятор, с всеми фазами, ограничениями/допущениями, либо ссылки на другую статью на русском языке где это хорошо описано. Такую статью можно было бы использовать как референсную, когда кто-то что-то забыл про симуляцию и решил освежить память и прочитать это на русском языке с хорошими примерами.

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


                                        Сейчас статья напоминает неформальное обсуждение за пивом.
                                        А: Слушай, ну помнишь, есть дельта-цикл, из-за этого мы ставим задержки.
                                        B: А, да, точно я что-то про это читал. Ну, да, вроде логично, спасибо, тоже буду ставить.

                                        Она не помогает разобраться и точно понять что происходит и почему, и как это перепроверить/доказать.

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

                                        Часть вторая (выводы)

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

                                        Как известно, самая важная часть в статье – это её заключение или вывод.

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

                                        Пример, который Вы привели слишком вымышленный. Сложно поверить, что в модуль заходит клок, из него делается второй клок и часть триггеров питается от одного “клока”, а часть от другого.

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

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

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

                                        Я допускаю, что есть кейс в реальном проекте где нужны задержки. Это может быть какой-то низкоскоростной протокол, где есть несколько клоков и данные как-то хитро передаются и защелкиваются, и там нужно клоки мультиплексировать.

                                        Если был бы разобран такой пример (со всеми констрейнами) и было бы “доказано”, что без использования задержек в конкретном случае нельзя (либо нужно написать в десять раз больше кода), то я бы согласился, что это полностью оправдано в данном конкретном случае, и использование задержек допустимо в модулях, где происходит некоторая “магия”, а когда уже все “синхронизировано” и идет поток данных, уже можно без них обойтись. Но из этого нельзя сделать вывод, что когда у меня один клок в системе и полностью синхронная схема я должен везде расставлять задержки.

                                        В комментариях Вы написали, что написание задержек это отчасти некоторое легаси, и Вы напоролись на некоторые проблемы из-за старого кода и теперь приходится их везде писать. Наследие неидеального кода – это проблема, которая встречалась многим разработчикам. В фразе “я делаю так, потому что у нас такое наследие, и нет бюджета всё исправить” я не вижу ничего плохого, такое, к сожалению, бывает, и идеальная картинка в книгах (советах из интернета) рушится с тем, что происходит в жизни.

                                        На самом деле вопрос же заключается в том, что если удалить все исходники, уволить всех коллег, оставить только Вас и набрать в FPGA команду студентов, которые ничего не знают, то Вы тоже будете им предлагать писать задержки во всех модулях и во всех проектах или нет? При условии, что Вы их руководитель/учитель/ментор и Вам дают большой бюджет и никто со сроками не торопит.

                                        TLDR: содержание статьи слабое, т.к. подробно не разобрано как работает симулятор. Вывод статьи дается на суперсложном странном примере, который может встретиться при плохой организации разработки или легаси. Его нельзя распространять на всю индустрию.
                                          0
                                          Я признателен всем участникам обсуждения, особенно old_bear, Des333, ishevchuk
                                          Ну и некоторые комментарии. Я не претендую на полное описание процесса симуляции. Идея статьи появилась в результате обсуждения вопросов для собеседования. Я придумал два вопроса и дал на них ответы. Но похоже по этим вопросам мало кто сможет пройти собеседование :-)
                                          Синтезатор задержки игнорирует. Это есть в стандарте
                                          1076.6 IEEE Standard for VHDL Register Transfer Level (RTL) Synthesis

                                          Вот выдержка из стандарта:



                                          Это есть в ug901-vivado-synthesis.pdf:


                                          Так что формально вставлять задержки в боевой код можно. В соответствии со стандартом синтезатор их проигнорирует.

                                          Интересный вопрос:
                                          На самом деле вопрос же заключается в том, что если удалить все исходники, уволить всех коллег, оставить только Вас и набрать в FPGA команду студентов, которые ничего не знают, то Вы тоже будете им предлагать писать задержки во всех модулях и во всех проектах или нет? При условии, что Вы их руководитель/учитель/ментор и Вам дают большой бюджет и никто со сроками не торопит.

                                          Да, я будет вставлять задержки и буду обучать учеников. Собственно это я делаю. Правда не все поддаются.
                                          За 18 лет я не смог привыкнуть к виду диаграммы в которой фронт клока и изменение сигнала визуально совпадают. Может надо ещё подождать?

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

                                          Ещё раз про то откуда берётся переприсвоение тактового сигнала. В основном это когда копипастом помещается код из другого компонента. Добавляется кусок кода, добавляются сигналы. Если название не совпадает, то и происходит переприсваивание. Другой вариант, если есть несколько источников а выбирается один. Имеется ввиду не мультиплексор а простое назначение сигнала. Это конечно можно сделать через alias но кто про него помнит?
                                          В основном это происходит на этапах финальной отладки проекта, когда про модель уже забывают.
                                          Поддержание проекта в виде пригодном для моделирования это достаточно сложная проблема. Но это уже тема для отдельной статьи.

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое