Modelado y sincronización de comportamiento en Verilog

Los modelos de comportamiento en Verilog contienen declaraciones de procedimiento, que controlan la simulación y manipulan variables de los tipos de datos. Todas estas declaraciones están incluidas en los procedimientos. Cada uno de los procedimientos tiene un flujo de actividad asociado.

Durante la simulación del modelo de comportamiento, todos los flujos definidos por las declaraciones 'siempre' e 'inicial' comienzan juntos en el tiempo de simulación 'cero'. Las instrucciones iniciales se ejecutan una vez y las instrucciones always se ejecutan repetitivamente. En este modelo, las variables de registro ayb se inicializan a 1 y 0 binarios respectivamente en el tiempo de simulación "cero". La instrucción inicial se completa y no se ejecuta de nuevo durante la ejecución de la simulación. Esta declaración inicial contiene un bloque de comienzo-fin (también llamado bloque secuencial) de declaraciones. En este bloque de tipo begin-end, a se inicializa primero seguido de b.

Ejemplo de modelado de comportamiento

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

Asignaciones de procedimiento

Las asignaciones de procedimiento son para actualizar las variables reg, integer, time y memory. Existe una diferencia significativa entre la asignación de procedimiento y la asignación continua como se describe a continuación:

Las asignaciones continuas controlan las variables netas y se evalúan y actualizan cada vez que un operando de entrada cambia de valor.

Las asignaciones de procedimiento actualizan el valor de las variables de registro bajo el control de las construcciones de flujo de procedimiento que las rodean.

El lado derecho de una asignación de procedimiento puede ser cualquier expresión que se evalúe como un valor. Sin embargo, las selecciones de piezas en el lado derecho deben tener índices constantes. El lado izquierdo indica la variable que recibe la asignación del lado derecho. El lado izquierdo de una asignación de procedimiento puede tomar una de las siguientes formas:

  • variable de registro, entero, real o de tiempo: una asignación a la referencia de nombre de uno de estos tipos de datos.

  • selección de bits de un registro, entero, real o variable de tiempo: una asignación a un solo bit que deja intactos los demás bits.

  • selección parcial de un registro, entero, variable real o de tiempo: selección parcial de dos o más bits contiguos que deja intactos el resto de los bits. Para el formulario de selección de parte, solo las expresiones constantes son legales.

  • elemento de memoria: una sola palabra de una memoria. Tenga en cuenta que las selecciones de bits y las selecciones de partes son ilegales en las referencias de elementos de memoria.

  • concatenación de cualquiera de los anteriores: se puede especificar una concatenación de cualquiera de las cuatro formas anteriores, lo que divide efectivamente el resultado de la expresión del lado derecho y asigna las partes de partición, en orden, a las diversas partes de la concatenación.

Retraso en la asignación (no para síntesis)

En una asignación retrasada Δt, las unidades de tiempo pasan antes de que se ejecute la instrucción y se realice la asignación de la izquierda. Con el retraso dentro de la asignación, el lado derecho se evalúa inmediatamente, pero hay un retraso de Δt antes de que el resultado se coloque en la asignación de la izquierda. Si otro procedimiento cambia una señal del lado derecho durante Δt, no afecta la salida. Las herramientas de síntesis no admiten retrasos.

Sintaxis

  • Procedural Assignmentvariable = expresión

  • Delayed assignment# Δt variable = expresión;

  • Intra-assignment delayvariable = # Δt expresión;

Ejemplo

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. */

Bloqueo de asignaciones

Se debe ejecutar una instrucción de asignación de procedimiento de bloqueo antes de la ejecución de las instrucciones que la siguen en un bloque secuencial. Una instrucción de asignación de procedimiento de bloqueo no impide la ejecución de instrucciones que la siguen en un bloque paralelo.

Sintaxis

La sintaxis para una asignación de procedimiento de bloqueo es la siguiente:

<lvalue> = <timing_control> <expression>

Donde, lvalue es un tipo de datos que es válido para una instrucción de asignación de procedimiento, = es el operador de asignación y el control de tiempo es el retardo opcional dentro de la asignación. El retardo del control de tiempo puede ser un control de retardo (por ejemplo, # 6) o un control de eventos (por ejemplo, @ (posedge clk)). La expresión es el valor del lado derecho que el simulador asigna al lado izquierdo. El operador de asignación = que se usa para bloquear asignaciones de procedimiento también se usa para asignaciones continuas de procedimiento y asignaciones continuas.

Ejemplo

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

Asignaciones sin bloqueo (RTL)

La asignación de procedimiento sin bloqueo le permite programar asignaciones sin bloquear el flujo de procedimiento. Puede utilizar la instrucción de procedimiento sin bloqueo siempre que desee realizar varias asignaciones de registros en el mismo paso de tiempo sin tener en cuenta el orden o la dependencia entre sí.

Sintaxis

La sintaxis para una asignación de procedimiento sin bloqueo es la siguiente:

<lvalue> <= <timing_control> <expression>

Donde lvalue es un tipo de datos que es válido para una instrucción de asignación de procedimiento, <= es el operador de asignación sin bloqueo y el control de tiempo es el control de tiempo opcional dentro de la asignación. El retardo del control de tiempo puede ser un control de retardo o un control de eventos (por ejemplo, @ (posedge clk)). La expresión es el valor del lado derecho que el simulador asigna al lado izquierdo. El operador de asignación sin bloqueo es el mismo operador que usa el simulador para el operador relacional menor o igual. El simulador interpreta el operador <= como un operador relacional cuando lo usa en una expresión, e interpreta el operador <= como un operador de asignación cuando lo usa en una construcción de asignación de procedimiento sin bloqueo.

Cómo evalúa el simulador las asignaciones de procedimiento sin bloqueo Cuando el simulador encuentra una asignación de procedimiento sin bloqueo, el simulador evalúa y ejecuta la asignación de procedimiento sin bloqueo en los dos pasos siguientes:

  • El simulador evalúa el lado derecho y programa la asignación del nuevo valor para que tenga lugar en un momento especificado por un control de tiempo de procedimiento. El simulador evalúa el lado derecho y programa la asignación del nuevo valor para que tenga lugar en un momento especificado por un control de tiempo de procedimiento.

  • Al final del paso de tiempo, en el que ha expirado el retardo dado o ha tenido lugar el evento correspondiente, el simulador ejecuta la asignación asignando el valor al lado izquierdo.

Ejemplo

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

Condiciones

La declaración condicional (o declaración if-else) se utiliza para tomar una decisión sobre si una declaración se ejecuta o no.

Formalmente, la sintaxis es la siguiente:

<statement> 
::= if ( <expression> ) <statement_or_null> 
||= if ( <expression> ) <statement_or_null> 
   else <statement_or_null> 
<statement_or_null> 

::= <statement> 
||= ;

Se evalúa la <expresión>; si es verdadero (es decir, tiene un valor conocido distinto de cero), se ejecuta la primera instrucción. Si es falso (tiene un valor cero o el valor es xoz), la primera instrucción no se ejecuta. Si hay una instrucción else y <expresión> es falsa, la instrucción else se ejecuta. Dado que el valor numérico de la expresión if se prueba para que sea cero, son posibles ciertos atajos.

Por ejemplo, las siguientes dos declaraciones expresan la misma lógica:

if (expression) 
if (expression != 0)

Dado que, la parte else de un if-else es opcional, puede haber confusión cuando se omite un else de una secuencia if anidada. Esto se resuelve asociando siempre el else con el anterior más cercano si carece de un else.

Ejemplo

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;

Construcción de: if- else- if

La siguiente construcción ocurre con tanta frecuencia que vale la pena una breve discusión por separado.

Example

if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else  
   <statement>

Esta secuencia de if (conocida como construcción if-else-if) es la forma más general de escribir una decisión multidireccional. Las expresiones se evalúan en orden; si alguna expresión es verdadera, la declaración asociada con ella se ejecuta y esto termina toda la cadena. Cada declaración es una sola declaración o un bloque de declaraciones.

La última parte else de la construcción if-else-if maneja el caso 'ninguno de los anteriores' o el caso predeterminado donde no se cumplió ninguna de las otras condiciones. A veces, no hay una acción explícita para el valor predeterminado; en ese caso, se puede omitir el else final o se puede usar para la verificación de errores para detectar una condición imposible.

Declaración de caso

La declaración de caso es una declaración de decisión especial de múltiples vías que prueba si una expresión coincide con una de varias otras expresiones y se ramifica en consecuencia. La declaración de caso es útil para describir, por ejemplo, la decodificación de una instrucción de microprocesador. La declaración del caso tiene la siguiente sintaxis:

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>

Las expresiones de casos se evalúan y comparan en el orden exacto en que se dan. Durante la búsqueda lineal, si una de las expresiones del elemento de caso coincide con la expresión entre paréntesis, se ejecuta la declaración asociada con ese elemento de caso. Si todas las comparaciones fallan y se proporciona el elemento predeterminado, se ejecuta la declaración del elemento predeterminado. Si no se proporciona la declaración predeterminada y todas las comparaciones fallan, no se ejecuta ninguna de las declaraciones del elemento de caso.

Aparte de la sintaxis, la declaración case difiere de la construcción if-else-if de múltiples vías de dos formas importantes:

  • Las expresiones condicionales en la construcción if-else-if son más generales que comparar una expresión con varias otras, como en la declaración case.

  • La declaración case proporciona un resultado definitivo cuando hay valores xyz en una expresión.

Declaraciones en bucle

Hay cuatro tipos de declaraciones en bucle. Proporcionan un medio para controlar la ejecución de una declaración cero, una o más veces.

  • siempre ejecuta continuamente una declaración.

  • repetir ejecuta una declaración un número fijo de veces.

  • while ejecuta una declaración hasta que una expresión se vuelve falsa. Si la expresión comienza falsa, la declaración no se ejecuta en absoluto.

  • para controlar la ejecución de sus declaraciones asociadas mediante un proceso de tres pasos, como sigue:

    • Ejecuta una asignación que normalmente se usa para inicializar una variable que controla el número de bucles ejecutados

    • Evalúa una expresión: si el resultado es cero, el ciclo for sale y, si no es cero, el ciclo for ejecuta sus declaraciones asociadas y luego realiza el paso 3

    • Ejecuta una asignación que normalmente se usa para modificar el valor de la variable de control de bucle, luego repite el paso 2

Las siguientes son las reglas de sintaxis para las declaraciones en bucle:

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 retardo

Control de retardo

La ejecución de una declaración de procedimiento se puede controlar mediante el uso de la siguiente sintaxis:

<statement> 
::= <delay_control> <statement_or_null> 
<delay_control> 
::= # <NUMBER> 
||= # <identifier> 
||= # ( <mintypmax_expression> )

El siguiente ejemplo retrasa la ejecución de la asignación en 10 unidades de tiempo:

# 10 rega = regb;

Los siguientes tres ejemplos proporcionan una expresión que sigue al signo de número (#). La ejecución de la asignación se demora por la cantidad de tiempo de simulación especificado por el valor de la expresión.

Control de eventos

La ejecución de una declaración de procedimiento se puede sincronizar con un cambio de valor en una red o registro, o la ocurrencia de un evento declarado, utilizando la siguiente sintaxis de control de eventos:

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> es una expresión que se resuelve en un valor de un bit.

Los cambios de valor en redes y registros se pueden utilizar como eventos para desencadenar la ejecución de una declaración. Esto se conoce como detección de un evento implícito. La sintaxis de Verilog también le permite detectar cambios según la dirección del cambio, es decir, hacia el valor 1 (posedge) o hacia el valor 0 (negedge). El comportamiento de posedge y negedge para valores de expresión desconocidos es el siguiente:

  • se detecta un borde en la transición de 1 a desconocido y de desconocido a 0
  • se detecta un posedge en la transición de 0 a desconocido y de desconocido a 1

Procedimientos: Bloques siempre y iniciales

Todos los procedimientos en Verilog se especifican dentro de uno de los siguientes cuatro bloques. 1) Bloques iniciales 2) Bloques siempre 3) Tarea 4) Función

Las declaraciones inicial y siempre se habilitan al comienzo de la simulación. Los bloques iniciales se ejecutan solo una vez y su actividad muere cuando finaliza la instrucción. Por el contrario, los bloques always se ejecutan repetidamente. Su actividad muere solo cuando se termina la simulación. No hay límite para el número de bloques iniciales y siempre que se pueden definir en un módulo. Las tareas y funciones son procedimientos que se habilitan desde uno o más lugares en otros procedimientos.

Bloques iniciales

La sintaxis de la declaración inicial es la siguiente:

<initial_statement> 
::= initial <statement>

El siguiente ejemplo ilustra el uso de la instrucción inicial para la inicialización de variables al inicio de la simulación.

Initial 
Begin 
   Areg = 0; // initialize a register 
   For (index = 0; index < size; index = index + 1) 
   Memory [index] = 0; //initialize a memory 
   Word 
End

Otro uso típico de los bloques iniciales es la especificación de descripciones de formas de onda que se ejecutan una vez para proporcionar estímulo a la parte principal del circuito que se está simulando.

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

Siempre bloquea

La declaración "siempre" se repite continuamente durante toda la ejecución de la simulación. La sintaxis de la declaración always se da a continuación

<always_statement> 
::= always <statement>

La declaración 'siempre', debido a su naturaleza en bucle, solo es útil cuando se usa junto con alguna forma de control de tiempo. Si una declaración "siempre" no proporciona ningún medio para que el tiempo avance, la declaración "siempre" crea una condición de bloqueo de la simulación. El siguiente código, por ejemplo, crea un bucle infinito de retardo cero:

Always areg = ~areg;

Proporcionar un control de tiempo al código anterior crea una descripción potencialmente útil, como en el siguiente ejemplo:

Always #half_period areg = ~areg;