
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:
- Dê um exemplo de código síncrono sem usar atrasos, o que fornecerá resultados diferentes ao modelar e ao trabalhar em equipamentos reais
- 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;
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
delta | clk1 | clk2 | re (clk1) | re (clk2) | b | c | d |
---|
0 0 | 1 | 0 0 | verdade | falsa | 0 0 | 0 0 | 0 0 |
---|
1 | 1 | 1 | falsa | verdade | 1 | 0 0 | 0 0 |
---|
2 | 1 | 0 0 | falsa | falsa | 1 | 0 0 | 1 |
---|
Nota: re - rising_edge
A tabela mostra que, no momento em que a função rising_edge (
clk2 ) é acionada, o valor de
b já
é 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
tempo | delta | clk1 | clk2 | re (clk1) | re (clk2) | b | c | d |
---|
25 | 0 0 | 1 | 0 0 | verdade | falsa | 0 0 | 0 0 | 0 0 |
---|
25 | 1 | 1 | 1 | falsa | verdade | 0 0 | 0 0 | 0 0 |
---|
26 | 0 0 | 1 | 1 | falsa | falsa | 1 | 0 0 | 0 0 |
---|
35 | 0 0 | 1 | 0 0 | verdade | falsa | 1 | 0 0 | 0 0 |
---|
35 | 1 | 1 | 1 | falsa | verdade | 1 | 0 0 | 0 0 |
---|
36. | 0 0 | 1 | 1 | falsa | falsa | 1 | 1 | 1 |
---|
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
- 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.