Apuntes sobre Verilog

Tema 3. Procedural Blocks

26 de diciembre de 2021

3. Procedural Blocks


Un bloque always se puede utilizar para modelar circuitos combinacionales o secuenciales.


Un circuito combinacional es aquel en el que la salida sólo depende de las entradas. En un circuito secuencial la salida depende del valor actual de las entradas y del valor previo de la salida.


El estilo de descripción de un circuito mediante su comportamiento utiliza asignaciones de procedimiento -procedural assignment- para modelar las asignaciones de señales basadas en un evento o transición de una señal.  Las declaraciones procedurales sólo pueden hacerse en los bloques de procedimiento always e initial, es decir, las procedural assignments se hacen dentro de un procedural block.


Dentro de un bloque always el valor asignado a una variable permanece constante hasta que otra declaración de procedimiento le asigne un nuevo valor. Esta es una diferencia importante respecto de las asignaciones continuas.

Lista de sensibilidades

Las declaraciones procedurales se basan en eventos, es decir, la asignación se hace cuando se cumple alguna de las condiciones descritas en la lista de sensibilidades de la declaración always @ (lista de sensibilidades)


Si no se especifica lista de eventos, el bloque always se ejecuta constantemente.

Si especificamos una lista de eventos, cuando cambie cualquier señal de la lista, se activa el bloque always y se ejecutan las declaraciones que contiene.


También se pueden emplear las palabras clave posedge y negedge para indicar que el bloque always se evalúe en el flanco positivo o negativo del reloj / señal deseada.


por ejemplo:

always @ (posedge clk) // con cada flanco positivo de reloj

always @ (!reset) // cada vez que reset = 0

always @ (in1, in2, sel) // cada vez que haya un cambio en las entradas


Dentro de un bloque always sólo se pueden hacer asignaciones a variables tipo reg, es decir, a la izquierda del signo = las variables deben ser tipo reg obligatoriamente, mientras que a la derecha del signo = pueden ser tipo wire o reg. 

Blocking / non-blocking assignments

Decíamos más arriba que dentro de un bloque always sólo se pueden hacer asignaciones a variables tipo reg.


Estas asignaciones pueden ser blocking cuando el circuito es combinacional (se denotan por el signo = ) o non-blocking (se denotan por el signo <= ) para circuitos secuenciales; circuitos síncronos con señal de reloj. No se deben mezclar asignaciones blocking y non-blocking dentro del mismo bloque.


En un lenguaje de descripción de hardware como Verilog hay lógica que puede ejecutarse de forma concurrente -toda a la vez- y lógica que se ejecuta una línea cada vez, por eso tiene que haber una forma de decir qué lógica es cuál y esa forma es mediante asignaciones blocking y non-blocking. 

3.1. Bloque always para modelar circuitos combinacionales

Las asignaciones de procedimiento -procedural assignment- también puede utilizarse para modelar circuitos lógicos combinacionales, haciendo las asignaciones cuando se produce un cambio en cualquiera de las entradas de la lista de sensibilidades.


Normas de diseño para circuitos combinacionales:

· Dentro de un bloque always sólo se pueden hacer asignaciones a variables tipo reg.

· Enumerar todas las entradas del sistema en la lista de sensibilidades.

· Para modelar circuitos combinacionales se utilizan principalmente asignaciones blocking.

Asignaciones blocking

Una asignación  blocking se denota con el símbolo =, significa que la evaluación y asignación de cada declaración tiene lugar inmediatamente. 


Las declaraciones se evalúan de arriba a abajo, en el orden en que están escritas. Las asignaciones por debajo permanecen bloqueadas hasta que se realiza la asignación actual. Si una variable actualiza su valor, este nuevo valor se utiliza para evaluar las declaraciones siguientes. 

module blocking;

// declaramos tres registros y los inicializamos a 0

 reg a = 0;

 reg b = 0;

 reg c = 0;

always @ (a) // el bloque always se ejecuta cuando a = 1

/*

   las asignaciones se hacen en orden descendente y las variables se van

   actualizando según se ejecutan las declaraciones.

*/ 

 begin

  b = a; // b = 1

  c = b; // c = 1

 end

endmodule

Si además la lista de sensibilidades contiene todas las entradas del sistema, este método sirve para modelar circuitos lógicos combinacionales. Es como si se hicieran asignaciones continuas fuera de un bloque de procedimiento.

Veamos algunos ejemplos.

Ejemplo 1. Puerta AND de tres entradas. 

Un bloque always se puede escribir de tres maneras distintas. 


1. Separando las variables de la lista de sensibilidades mediante comas.


module and_gate_3_inputs ( input in1, in2, in3, output reg out);

always @ (in1, in2, in3)

 out = (in1 & in2 & in3);

endmodule


2. Sustituyendo las comas que separan las variables de la lista de sensibilidades por la palabra or


module and_gate_3_inputs ( input in1, in2, in3, output reg out);

always @ (in1 or in2 or in3)

 out = (in1 & in2 & in3);

endmodule


3. Con un asterisco. El (*) dentro de la lista de sensibilidades, indica que el bloque debe ejecutarse cuando cualquiera de las señales que contiene cambie de valor, es decir  in1, in2, in3


module and_gate_3_inputs ( input in1, in2, in3, output reg out);

always @ (*)

 out = (in1 & in2 & in3);

endmodule

Ejemplo 2. Mux 2-1 

module multiplexor_2_1 ( input in1, in2, sel, output reg out);

always @ (in1, in2, sel)

 out = (in1 & ~sel) | (in2 & sel);

endmodule

3.2 Bloque always para modelar circuitos secuenciales

Decíamos al principio de este tema que en  un circuito secuencial la salida depende del valor actual de las entradas y del valor previo de la salida. Esto implica que el circuito debe tener algún tipo de memoria interna que almacene dicho valor de la salida. Esa memoria está hecha de flip-flops. Un flip-flop almacena 1 bit de memoria, luego el flip-flop es la unidad básica de memoria.


Algunos ejemplos de circuitos secuenciales son: contadores, memorias, flip-flops…Los circuitos secuenciales están formados por circuitos combinacionales más una memoria.

Los circuitos secuenciales pueden ser de dos tipos: 


1. Asíncronos cuando no dependen de una señal de reloj; la salida cambia en cuanto cambian las entradas. 


2. Síncronos cuando dependen de una señal de reloj. Los cambios tienen lugar sincronizados con el flanco del reloj.


Normas de diseño para circuitos secuenciales:

· Dentro de un bloque always sólo se pueden hacer asignaciones a variables tipo reg.

· Enumerar sólamente la señal de reloj y reset (si hubiera) en la lista de sensibilidades.

· Para modelar circuitos secuenciales se utilizan principalmente asignaciones non-blocking 

Asignaciones non-blocking


Una asignación non-blocking se indica con el símbolo <=


Las declaraciones non-blocking se evalúan con los valores que tienen las variables cuando se activa el bloque always. Así, una variable dada tiene el mismo valor para todas las declaraciones del bloque. 


Cuando se utilizan asignaciones non-blocking, todas las declaraciones se evalúan a la vez, pero los reg a la izquierda del signo <= no se actualizan hasta que no finaliza la ejecución del bloque


Dicho de otra forma, non-blocking significa que el resultado de cada asignación se ve al final del bloque; los reg a la izquierda del signo <= se actualizan cuando finaliza la ejecución del bloque.


module non_blocking;

// declaramos tres registros y los inicializamos a 0

 reg a = 0;

 reg b = 0;

 reg c = 0;

always @ (a) // el bloque always se ejecuta cuando a = 1

/*

   las asignaciones se hacen con el valor de las variables en el momento

   en que se activa el bloque always

*/ 

 begin

  b <= a; // b = 1

  c <= b; // c = 0

 end

endmodule



Este método sirve para modelar circuitos lógicos secuenciales cuando se combina con la activación del bloque always a partir de una señal de reloj: always @ (posedge clk)


Veamos algunos ejemplos.

Ejemplo 1. Flip Flop tipo D 

module DFlipFlop (output reg Q, Qn, input wire D, clk);

always @(posedge clk)

 begin

   Q <= D;

   Qn <= ~D;

 end

endmodule

Ejemplo 2. Flip Flop tipo D con Reset asíncrono 

module DFlipFlop (output reg Q, Qn, input wire D, clk, rst);

always @(posedge clk or negedge rst)

 if (!rst)

   begin

     Q <= 1'b0;

     Qn <= 1'b1;

   end

 else

   begin

     Q <= D;

     Qn <= ~D;

   end

endmodule

Recursos

Open FPGA Verilog tutorial Capítulo 22: Reglas de diseño síncrono