Mais uma vez sobre atrasos no código-fonte do projeto FPGA ou uma pergunta simples para uma entrevista para um trabalho de desenvolvedor de FPGA



Há algum tempo, durante uma discussão na companhia de desenvolvedores profissionais de FPGA, surgiu uma discussão sobre a aprovação de uma entrevista. Que perguntas são feitas lá e o que pode ser feito. Sugeri duas perguntas:

  1. Dê um exemplo de código síncrono sem usar atrasos, o que fornecerá resultados diferentes ao modelar e ao trabalhar em equipamentos reais
  2. Corrija esse código com atrasos.

Após essa pergunta, iniciou-se uma animada discussão, como resultado da qual decidi considerar esse assunto com mais detalhes.

Eu já falei um pouco sobre esse assunto no artigo anterior . Agora com mais detalhes. Aqui está um texto de exemplo:

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; 

Para simplificar, todo o código é colocado em um componente.

Os sinais clk1 e a são sinais de exposição de teste. clk1 é uma frequência de clock de 100 MHz, o sinal a mantém dois ciclos de clock em 0 e quatro ciclos de clock em 1. O sinal a é gerado com um atraso de 1 nc em relação à borda ascendente de clk1 . Esses dois sinais são suficientes para descrever o problema.

Diferentes opções de código sintetizado podem ser descomentadas e modeladas.
Considere a primeira opção, este é um código sintetizado sem demora e usando a reatribuição da frequência do relógio.

Aqui estão os resultados da simulação para a opção 1:



O diagrama mostra visualmente que os sinais de relógio clk1 e clk2 coincidem, mas na verdade clk2 é atrasado em relação a clk1 pelo valor do atraso delta. O sinal c atrasa o sinal b em um ciclo de relógio. Isto está correto. Mas o sinal d deve coincidir com o sinal c , mas isso não acontece. Funciona mais cedo.

Vamos lembrar o que é o atraso delta. Esse é um conceito fundamental, baseado no trabalho de simuladores de eventos, que usamos ao modelar circuitos lógicos.

O simulador tem o conceito de tempo do modelo. Todos os eventos no sistema são anexados a esse tempo de modelo. Vejamos a formação da frequência do relógio:

 clk1 <= not clk1 after 5 ns; 

Suponha que agora estamos modelando apenas clk1 , não há outros sinais.
No momento inicial, clk1 é 0, é definido quando o sinal é declarado. O simulador vê um requisito para inverter o sinal. A palavra-chave after fornece instruções para atribuir um novo valor em 5 ns em relação ao tempo atual do modelo. O simulador vê isso e observa que, no tempo 5 ns, o valor de clk1 será 1. Embora esse seja um futuro modelo, pela maneira como ainda pode mudar. Em seguida, o simulador verifica os sinais restantes. O simulador verá que, para um dado momento no tempo do modelo, tudo está pronto e ele pode calcular o próximo momento. Surge a pergunta - qual é o próximo momento? Em princípio, são possíveis diferentes opções. Por exemplo, o Simulink possui um modo de afinação fixa. Nesse caso, o tempo do modelo será incrementado em alguma quantia e os cálculos continuarão.

Os sistemas de simulação de circuitos digitais são diferentes. Eles passam para o próximo evento, que eles já colocaram no futuro em seu eixo de tempo do modelo. Nesse caso, serão 5 ns. O simulador verá que clk1 mudou e calculará um novo valor para ele; será 0, que também será colocado com um atraso de 5 ns no eixo do tempo. I.e. será 10 ns. E assim o processo continuará até que o tempo de simulação especificado termine.

Agora vamos adicionar os sinais a e b .

O sinal a é atribuído no processo. Para o sinal b , a construção condicional quando é usada; A função rising_edge ( clk1 ) analisa clk1 e retorna true quando a frente é fixa, ou seja, o valor anterior é 0 e o valor atual é 1.

No tempo do modelo 5 ns, o clk1 será alterado. Ele se tornará igual a 1 e, no momento de 10 ns, será criado um evento para defini-lo como 0. Mas isso é mais tarde. Enquanto ainda estamos no momento de 5 ns e continuamos os cálculos. O simulador vai para a linha
 b<=a when rising_edge(clk1); 
Como existe uma função que depende de clk1, o simulador calculará o valor da função, veja se ela retorna verdadeira e atribui
 b<=a; 


Aqui começa a parte mais interessante - quando é necessário alterar o valor de b . Parece necessário mudar isso agora, neste momento. Mas temos processos paralelos. Talvez ainda precisemos do valor de b para calcular outros sinais. E aqui vem o conceito de atraso delta. Este é o valor mínimo pelo qual o tempo do modelo muda. Este valor nem sequer tem a dimensão do tempo. Este é apenas um delta. Mas pode haver muitos deles. E tanto que o simulador simplesmente pára por engano ou congela.
Portanto, um novo valor de b será definido para o momento 5 ns + 1 (1 é o primeiro atraso delta). O simulador verá que já não há nada para calcular para o momento 5 ns e passará para o próximo momento, e será 5 ns + 1; Neste momento, rising_edge (ckl1) não funciona. E o valor de b será definido como 1. Depois disso, o simulador passará para o momento 10 nc.

Agora vamos adicionar os sinais c , d e ver por que eles são diferentes.
É melhor considerar o momento do tempo do modelo 25 ns, levando em consideração os atrasos delta

deltaclk1clk2re (clk1)re (clk2)bcd
0 010 0verdadefalsa0 00 00 0
111falsaverdade10 00 0
210 0falsafalsa10 01

Nota: re - rising_edge

A tabela mostra que, no momento em que a função rising_edge ( clk2 ) é acionada, o valor de bé 1. E, portanto, será atribuído ao sinal d .

Com base no senso comum, esse não é o comportamento que esperávamos do código. Afinal, simplesmente reatribuímos o sinal clk1 a clk2 e esperamos que os sinais c e d fossem os mesmos. Mas seguindo a lógica do simulador, não é assim. Este é um recurso PRINCIPAL . É claro que esse recurso deve ser conhecido pelos desenvolvedores de projetos FPGA e, portanto, essa é uma pergunta boa e necessária para uma entrevista.

O que acontecerá durante a síntese? Mas o sintetizador seguirá o senso comum, fará dos sinais clk2 e clk1 um sinal e, portanto, c e d também serão os mesmos. E com certas configurações de sintetizador, elas também serão combinadas em um sinal.

Este é apenas o caso quando a modelagem e o trabalho em equipamentos reais levarão a resultados diferentes. Quero observar que a razão para os diferentes resultados é a lógica diferente do simulador e sintetizador. Essa é uma diferença BÁSICA. Isso não tem nada a ver com restrições de tempo. E se o seu projeto no modelo e no ferro mostra resultados diferentes, verifique, talvez um design como esse apareceu lá

 clk2 <= clk1 

Agora, a segunda pergunta é corrigir esse código com atrasos.
Esta é a opção 2. Pode ser descomentada e modelada.
Aqui está o resultado.



O resultado está correto. O que aconteceu? Vamos fazer uma mesa novamente por um intervalo de 25 - 36 ns
tempodeltaclk1clk2re (clk1)re (clk2)bcd
250 010 0verdadefalsa0 00 00 0
25111falsaverdade0 00 00 0
260 011falsafalsa10 00 0
350 010 0verdadefalsa10 00 0
35111falsaverdade10 00 0
36.0 011falsafalsa111

Pode-se observar que o valor de b não muda nos momentos das frentes clk1 , clk2 . Um atraso de 1 ns leva o momento em que o sinal muda além da zona de resposta da borda. Este código está se aproximando da realidade. Em um circuito real, há algum tempo para o gatilho disparar e para o sinal se propagar. Esse tempo deve ser menor que o período da frequência do relógio; de fato, é isso que o rastreador faz e é o que a análise de tempo verifica.

A causa do erro é a reatribuição do sinal do relógio pela atribuição usual na qual um atraso delta aparece. No entanto, a linguagem VHDL possui uma construção de alias. Isso permite que você obtenha um nome diferente para o sinal. Aqui está o anúncio:

 alias clk3 : std_logic is clk1; 

No texto de exemplo, você pode descomentar a opção 3 - ela funcionará corretamente.

Este exemplo está escrito em VHDL. Talvez este seja o problema apenas dessa linguagem? Mas aqui estão as mesmas opções no Verilog.

Texto oculto
 `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 



  • Opção 1 - sem demora. Não funciona corretamente.
  • Opção 2 - com atrasos. Funciona corretamente.
  • Opção 3 - reatribuição por fio. Funciona corretamente.

Verilog tem o conceito de reg e wire. Nesse caso, a reatribuição do sinal do relógio através do fio parece mais natural. Isso é análogo a uma atribuição de alias no VHDL. Isso alivia um pouco a tensão do problema, mas você ainda precisa saber disso.
A Verilog também tem o conceito de atribuição de bloqueio e não-bloqueio. A atribuição de sinal bec pode ser escrita de outra maneira:

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

E você pode fazer isso:

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

Dependendo da ordem das linhas, o resultado será diferente.

Voltando ao tópico da entrevista, quero enfatizar mais uma vez que essas perguntas são para entender a essência do problema. E a partir da compreensão do problema, pode-se tirar várias conclusões, por exemplo, qual estilo de código usar. Pessoalmente, eu sempre uso a atribuição de atraso.

Arquivos de amostra estão disponíveis aqui.

Source: https://habr.com/ru/post/pt419875/


All Articles