Modelagem Comportamental e Tempo em Verilog
Os modelos comportamentais em Verilog contêm instruções procedimentais, que controlam a simulação e manipulam variáveis dos tipos de dados. Todas essas declarações estão contidas nos procedimentos. Cada um dos procedimentos possui um fluxo de atividades associado a ele.
Durante a simulação do modelo comportamental, todos os fluxos definidos pelas declarações 'sempre' e 'inicial' começam juntos no tempo de simulação 'zero'. As instruções iniciais são executadas uma vez e as instruções always são executadas repetidamente. Neste modelo, as variáveis de registro aeb são inicializadas para os binários 1 e 0 respectivamente no tempo de simulação 'zero'. A instrução inicial é então concluída e não é executada novamente durante a simulação. Esta instrução inicial contém um bloco de início e fim (também chamado de bloco sequencial) de instruções. Neste bloco de tipo começo-fim, a é inicializado primeiro seguido por b.
Exemplo de modelagem comportamental
module behave;
reg [1:0]a,b;
initial
begin
a = ’b1;
b = ’b0;
end
always
begin
#50 a = ~a;
end
always
begin
#100 b = ~b;
end
End module
Atribuições Processuais
As atribuições procedurais são para atualizar as variáveis reg, integer, time e memory. Há uma diferença significativa entre a atribuição procedimental e a atribuição contínua, conforme descrito abaixo -
As atribuições contínuas conduzem as variáveis da rede e são avaliadas e atualizadas sempre que um operando de entrada muda de valor.
As atribuições procedurais atualizam o valor das variáveis de registro sob o controle das construções de fluxo procedural que as cercam.
O lado direito de uma atribuição procedural pode ser qualquer expressão avaliada como um valor. No entanto, as seleções de parte do lado direito devem ter índices constantes. O lado esquerdo indica a variável que recebe a atribuição do lado direito. O lado esquerdo de uma atribuição procedimental pode assumir uma das seguintes formas -
variável de registro, inteiro, real ou de tempo - Uma atribuição à referência de nome de um desses tipos de dados.
bit-select de um registro, inteiro, real ou variável de tempo - Uma atribuição a um único bit que deixa os outros bits intocados.
seleção parcial de um registrador, inteiro, real ou variável de tempo - uma seleção parcial de dois ou mais bits contíguos que deixa o resto dos bits intocados. Para o formulário de seleção parcial, apenas expressões constantes são válidas.
elemento de memória - uma única palavra de uma memória. Observe que as seleções de bits e partes são ilegais em referências de elementos de memória.
concatenação de qualquer uma das opções acima - uma concatenação de qualquer uma das quatro formas anteriores pode ser especificada, o que particiona efetivamente o resultado da expressão do lado direito e atribui as partes da partição, em ordem, às várias partes da concatenação.
Atraso na Atribuição (não para síntese)
Em uma atribuição atrasada Δt unidades de tempo passam antes que a instrução seja executada e a atribuição à esquerda seja feita. Com o atraso intra-atribuição, o lado direito é avaliado imediatamente, mas há um atraso de Δt antes que o resultado seja colocado na atribuição à esquerda. Se outro procedimento alterar um sinal do lado direito durante Δt, ele não afetará a saída. Atrasos não são suportados por ferramentas de síntese.
Sintaxe
Procedural Assignmentvariável = expressão
Delayed assignment# Δt variável = expressão;
Intra-assignment delayvariável = # expressão Δt;
Exemplo
reg [6:0] sum; reg h, ziltch;
sum[7] = b[7] ^ c[7]; // execute now.
ziltch = #15 ckz&h; /* ckz&a evaluated now; ziltch changed
after 15 time units. */
#10 hat = b&c; /* 10 units after ziltch changes, b&c is
evaluated and hat changes. */
Bloqueio de atribuições
Uma instrução de atribuição procedural de bloqueio deve ser executada antes da execução das instruções que a seguem em um bloco sequencial. Uma instrução de atribuição procedural de bloqueio não impede a execução de instruções que a seguem em um bloco paralelo.
Sintaxe
A sintaxe para uma atribuição de procedimento de bloqueio é a seguinte -
<lvalue> = <timing_control> <expression>
Onde, lvalue é um tipo de dados que é válido para uma instrução de atribuição procedural, = é o operador de atribuição e o controle de tempo é o atraso opcional intra-atribuição. O atraso de controle de tempo pode ser um controle de atraso (por exemplo, # 6) ou um controle de evento (por exemplo, @ (clk posedge)). A expressão é o valor do lado direito que o simulador atribui ao lado esquerdo. O operador de atribuição = usado para bloquear atribuições procedurais também é usado por atribuições contínuas procedurais e atribuições contínuas.
Exemplo
rega = 0;
rega[3] = 1; // a bit-select
rega[3:5] = 7; // a part-select
mema[address] = 8’hff; // assignment to a memory element
{carry, acc} = rega + regb; // a concatenation
Atribuições não bloqueadoras (RTL)
A atribuição de procedimento sem bloqueio permite que você agende atribuições sem bloquear o fluxo de procedimento. Você pode usar a instrução de procedimento sem bloqueio sempre que quiser fazer várias atribuições de registro no mesmo intervalo de tempo, sem levar em conta a ordem ou dependência umas das outras.
Sintaxe
A sintaxe para uma atribuição procedural sem bloqueio é a seguinte -
<lvalue> <= <timing_control> <expression>
Onde lvalue é um tipo de dados válido para uma instrução de atribuição procedural, <= é o operador de atribuição sem bloqueio e o controle de tempo é o controle de tempo opcional intra-atribuição. O atraso de controle de tempo pode ser um controle de atraso ou um controle de evento (por exemplo, @ (clk posedge)). A expressão é o valor do lado direito que o simulador atribui ao lado esquerdo. O operador de atribuição sem bloqueio é o mesmo operador que o simulador usa para o operador relacional menor que ou igual. O simulador interpreta o operador <= como um operador relacional quando você o usa em uma expressão e interpreta o operador <= como um operador de atribuição quando você o usa em uma construção de atribuição procedural sem bloqueio.
Como o simulador avalia atribuições de procedimento sem bloqueio Quando o simulador encontra uma atribuição de procedimento sem bloqueio, o simulador avalia e executa a atribuição de procedimento sem bloqueio em duas etapas da seguinte forma -
O simulador avalia o lado direito e programa a atribuição do novo valor para ocorrer em um momento especificado por um controle de tempo de procedimento. O simulador avalia o lado direito e programa a atribuição do novo valor para ocorrer em um momento especificado por um controle de tempo de procedimento.
No final da etapa de tempo, na qual o atraso determinado expirou ou o evento apropriado ocorreu, o simulador executa a atribuição atribuindo o valor ao lado esquerdo.
Exemplo
module evaluates2(out);
output out;
reg a, b, c;
initial
begin
a = 0;
b = 1;
c = 0;
end
always c = #5 ~c;
always @(posedge c)
begin
a <= b;
b <= a;
end
endmodule
Condições
A instrução condicional (ou instrução if-else) é usada para decidir se uma instrução é executada ou não.
Formalmente, a sintaxe é a seguinte -
<statement>
::= if ( <expression> ) <statement_or_null>
||= if ( <expression> ) <statement_or_null>
else <statement_or_null>
<statement_or_null>
::= <statement>
||= ;
A <expressão> é avaliada; se for verdadeiro (ou seja, tiver um valor conhecido diferente de zero), a primeira instrução será executada. Se for falso (tem valor zero ou o valor é x ou z), a primeira instrução não é executada. Se houver uma instrução else e <expression> for falsa, a instrução else é executada. Visto que o valor numérico da expressão if é testado para ser zero, certos atalhos são possíveis.
Por exemplo, as duas declarações a seguir expressam a mesma lógica -
if (expression)
if (expression != 0)
Visto que a parte else de um if-else é opcional, pode haver confusão quando um else é omitido de uma sequência if aninhada. Isso é resolvido associando sempre o else ao anterior mais próximo, se esse não tiver um else.
Exemplo
if (index > 0)
if (rega > regb)
result = rega;
else // else applies to preceding if
result = regb;
If that association is not what you want, use a begin-end block statement
to force the proper association
if (index > 0)
begin
if (rega > regb)
result = rega;
end
else
result = regb;
Construção de: if- else- if
A construção a seguir ocorre com tanta frequência que vale a pena uma breve discussão separada.
Example
if (<expression>)
<statement>
else if (<expression>)
<statement>
else if (<expression>)
<statement>
else
<statement>
Essa sequência de if (conhecida como construção if-else-if) é a maneira mais geral de escrever uma decisão de múltiplas vias. As expressões são avaliadas em ordem; se alguma expressão for verdadeira, a instrução associada a ela é executada e termina toda a cadeia. Cada declaração é uma declaração única ou um bloco de declarações.
A última parte else da construção if-else-if lida com 'nenhum dos anteriores' ou caso padrão em que nenhuma das outras condições foi satisfeita. Às vezes, não há uma ação explícita para o padrão; nesse caso, o else posterior pode ser omitido ou pode ser usado para a verificação de erros para capturar uma condição impossível.
Declaração do Caso
A declaração de caso é uma declaração de decisão multi-vias especial que testa se uma expressão corresponde a uma de várias outras expressões e se ramifica de acordo. A instrução case é útil para descrever, por exemplo, a decodificação de uma instrução de microprocessador. A instrução case tem a seguinte sintaxe -
Example
<statement>
::= case ( <expression> ) <case_item>+ endcase
||= casez ( <expression> ) <case_item>+ endcase
||= casex ( <expression> ) <case_item>+ endcase
<case_item>
::= <expression> <,<expression>>* : <statement_or_null>
||= default : <statement_or_null>
||= default <statement_or_null>
As expressões de caso são avaliadas e comparadas na ordem exata em que são fornecidas. Durante a procura linear, se uma das expressões de item de caso corresponder à expressão entre parênteses, a instrução associada a esse item de caso será executada. Se todas as comparações falharem e o item padrão for fornecido, a instrução do item padrão será executada. Se a instrução padrão não for fornecida e todas as comparações falharem, nenhuma das instruções do item de caso será executada.
Além da sintaxe, a instrução case difere da construção if-else-if multi-way de duas maneiras importantes -
As expressões condicionais na construção if-else-if são mais gerais do que comparar uma expressão com várias outras, como na instrução case.
A instrução case fornece um resultado definitivo quando há valores x e z em uma expressão.
Demonstrações de Looping
Existem quatro tipos de instruções de loop. Eles fornecem um meio de controlar a execução de uma instrução zero, uma ou mais vezes.
para sempre executa continuamente uma instrução.
repeat executa uma instrução um número fixo de vezes.
while executa uma instrução até que uma expressão se torne falsa. Se a expressão começar com falsa, a instrução não será executada.
para a execução de controles de suas instruções associadas por um processo de três etapas, como segue -
Executa uma atribuição normalmente usada para inicializar uma variável que controla o número de loops executados
Avalia uma expressão - se o resultado for zero, o loop for termina, e se não for zero, o loop for executa sua (s) instrução (ões) associada (s) e, em seguida, realiza a etapa 3
Executa uma atribuição normalmente usada para modificar o valor da variável loopcontrol e, em seguida, repete a etapa 2
A seguir estão as regras de sintaxe para as instruções de loop -
Example
<statement>
::= forever <statement>
||=forever
begin
<statement>+
end
<Statement>
::= repeat ( <expression> ) <statement>
||=repeat ( <expression> )
begin
<statement>+
end
<statement>
::= while ( <expression> ) <statement>
||=while ( <expression> )
begin
<statement>+
end
<statement>
::= for ( <assignment> ; <expression> ; <assignment> )
<statement>
||=for ( <assignment> ; <expression> ; <assignment> )
begin
<statement>+
end
Controles de atraso
Controle de Atraso
A execução de uma instrução de procedimento pode ser controlada por atraso usando a seguinte sintaxe -
<statement>
::= <delay_control> <statement_or_null>
<delay_control>
::= # <NUMBER>
||= # <identifier>
||= # ( <mintypmax_expression> )
O exemplo a seguir atrasa a execução da atribuição em 10 unidades de tempo -
# 10 rega = regb;
Os próximos três exemplos fornecem uma expressão após o sinal de número (#). A execução da atribuição atrasa pelo tempo de simulação especificado pelo valor da expressão.
Controle de Eventos
A execução de uma instrução procedural pode ser sincronizada com uma mudança de valor em uma rede ou registro, ou a ocorrência de um evento declarado, usando a seguinte sintaxe de controle de evento -
Example
<statement>
::= <event_control> <statement_or_null>
<event_control>
::= @ <identifier>
||= @ ( <event_expression> )
<event_expression>
::= <expression>
||= posedge <SCALAR_EVENT_EXPRESSION>
||= negedge <SCALAR_EVENT_EXPRESSION>
||= <event_expression> <or <event_expression>>
* <SCALAR_EVENT_EXPRESSION> é uma expressão que resolve para um valor de um bit.
As alterações de valor em redes e registradores podem ser usadas como eventos para disparar a execução de uma instrução. Isso é conhecido como detecção de um evento implícito. A sintaxe Verilog também permite detectar mudanças com base na direção da mudança - isto é, em direção ao valor 1 (posedge) ou em direção ao valor 0 (negedge). O comportamento de posedge e negedge para valores de expressão desconhecidos é o seguinte -
- um negedge é detectado na transição de 1 para desconhecido e de desconhecido para 0
- um posedge é detectado na transição de 0 para desconhecido e de desconhecido para 1
Procedimentos: Sempre e Blocos Iniciais
Todos os procedimentos em Verilog são especificados em um dos quatro blocos a seguir. 1) Blocos iniciais 2) Blocos sempre 3) Tarefa 4) Função
As declarações inicial e sempre são ativadas no início da simulação. Os blocos iniciais são executados apenas uma vez e sua atividade termina quando a instrução termina. Em contraste, os blocos always são executados repetidamente. Sua atividade morre apenas quando a simulação é encerrada. Não há limite para o número de blocos iniciais e sempre que podem ser definidos em um módulo. Tarefas e funções são procedimentos que são ativados em um ou mais locais em outros procedimentos.
Blocos Iniciais
A sintaxe para a declaração inicial é a seguinte -
<initial_statement>
::= initial <statement>
O exemplo a seguir ilustra o uso da instrução inicial para inicialização de variáveis no início da simulação.
Initial
Begin
Areg = 0; // initialize a register
For (index = 0; index < size; index = index + 1)
Memory [index] = 0; //initialize a memory
Word
End
Outro uso típico dos blocos iniciais é a especificação de descrições de forma de onda que são executadas uma vez para fornecer estímulo à parte principal do circuito que está sendo simulado.
Initial
Begin
Inputs = ’b000000;
// initialize at time zero
#10 inputs = ’b011001; // first pattern
#10 inputs = ’b011011; // second pattern
#10 inputs = ’b011000; // third pattern
#10 inputs = ’b001000; // last pattern
End
Sempre bloqueia
A declaração 'sempre' se repete continuamente durante toda a execução da simulação. A sintaxe para a instrução always é fornecida abaixo
<always_statement>
::= always <statement>
A instrução 'always', por causa de sua natureza de loop, só é útil quando usada em conjunto com alguma forma de controle de tempo. Se uma declaração 'sempre' não fornece meios para o tempo avançar, a declaração 'sempre' cria uma condição de impasse de simulação. O código a seguir, por exemplo, cria um loop infinito de atraso zero -
Always areg = ~areg;
Fornecer um controle de tempo para o código acima cria uma descrição potencialmente útil - como no exemplo a seguir -
Always #half_period areg = ~areg;