Tabla de Contenidos

Verilog HDL

Introducción

Verilog HDL es uno de los dos lenguajes de descripción de hardware (HDL, del inglés Hardware Description Language) más comunes que utilizan los diseñadores de circuitos integrados (IC), con una sintaxis derivada del lenguaje de programación C. Se corresponde inicialmente con el IEEE standard 1364-1995, extendido posteriormente en IEEE standard 1364-2001 y SystemVerilog.

Un HDL permite simular el diseño en etapas tempranas del ciclo de diseño para corregir errores o experimentar con diferentes arquitecturas, llevar a cabo la síntesis lógica del mismo y facilitar la documentación del mismo. Los diseños descritos en HDL son independientes de la tecnología, fáciles de diseñar y depurar, y suelen ser más legibles que los esquemas, especialmente para circuitos grandes y complejos, lo que permite además un prototipado rápido y acelerar el tiempo de salida del producto final (TTM, del inglés Time-To-Market).

Verilog se puede utilizar para describir diseños en cuatro niveles de abstracción:

Los últimos tres niveles se corresponden con una descripción también denominada estructural, si bien el lenguaje permite llevar a cabo descripciones mixtas que combinan parte comportamental y estructural, así como las construcciones que pueden utilizarse para controlar la entrada y salida de la simulación.

Es común actualmente utilizar descripciones Verilog como entrada para herramientas de síntesis lógica que generan descripciones a nivel de puerta o netlist del circuito, si bien algunas construcciones de Verilog no son sintetizables y es importante diferenciar ambos tipos. También la manera de escribir el código afectará en gran medida al tamaño y la velocidad del circuito sintetizado. Como en la mayoría de los casos se pretenderá sintetizar los circuitos, las construcciones no sintetizables deben usarse sólo para bancos de pruebas (testbenches). Se trata de módulos de programa utilizados para generar las E/S necesarias para simular el resto del diseño normalmente bajo el modelo estímulo/respuesta.

Como en la mayoría de HDLs se distinguen dos tipos/estilos de codificación/modelado/descripción que tienen correspondencia con determinados niveles de abstracción señalados:

assign a = b & c | d;
assign d = e & (~c);
always @(posedge clk) 
count <= count+1;

El estilo procedural es preferido por los diseñadores por la facilidad de codifificación y semejanza con los lenguajes de programación de alto nivel (HLL, del inglés High Level Language), si bien es importante tener en cuenta que la mayoría de construcciones procedurales con asignación implicarán la generación de almacenamiento en síntesis y esto puede no ser deseable en algunos casos donde se desea evitar lógica superflua atendiendo a restricciones de área o de temporización, por lo que se recomienda hacer uso del estilo estructural en circuitos puramente combinacionales.

Sintaxis Verilog

Componentes léxicos

Los archivos de código fuente Verilog constan de los siguientes componentes (tokens) léxicos:

Espaciadores

Los espacios en blanco separan las palabras y pueden ser: espacios, tabuladores, caracteres de nueva línea y de retorno de carro. Así, una sentencia puede extenderse sobre múltiples líneas sin caracteres de continuación especiales.

Comentarios

Los comentarios se pueden especificar de dos maneras (exactamente de la misma manera que en C/C++):

a = c+d; // este es un comentario simple 
/* sin embargo, este comentario continua en  
   en más de una línea */ 
assign y = temp_reg; 
assign x = ABC /*más su complemento*/ + ABC_

Números

El almacenamiento de números se define con un número de bits y el correspondiente valor que puede especificarse en binario, octal, decimal o hexadecimal.

Ejemplos

Identificadores

Los identificadores son palabras definidas por el usuario para variables, nombres de funciones, nombres de módulos, nombres de bloques y nombres de instancia.
Los identificadores comienzan con una letra o subrayado (no con un número o $) y pueden incluir cualquier número de letras, dígitos y subrayados.
Los identificadores en Verilog distinguen entre mayúsculas y minúsculas.

Ejemplos

adder           // usa subrayados para hacer 
by_8_shifter    // los identificadores más legibles 
_ABC_ /* no es lo mismo que */ _abc_ 
Read_ // suele usarse para indicar NOT Read

Operadores

Los operadores son uno, dos, y a veces tres caracteres con símbolos especiales, p.ej. >, +, ~, &, !, =, utilizados para realizar operaciones sobre variables.
El conjunto de operadores y su uso se describen en detalle en apartados posteriores.

Palabras clave Verilog

Estas son palabras que tienen un significado especial en Verilog. Algunos ejemplos son assign, case, while, wire, reg, and, or, nand, y module, la lista completa se verá en lo sucesivo.
No deben utilizarse como identificadores.
Las palabras clave de Verilog también incluyen directivas del compilador, tareas y funciones del sistema.

Estructura de módulos

El diseño de circuitos y sistemas digitales con Verilog HDL pasa siempre por utilización jerárquica de módulos como elemento principal del lenguaje y la descripción a distintos niveles de abstracción y estilos del mismo.

directivas_compilador
module nombre_de_modulo();
    // interfaz (puertos)
    inputoutputinout// conexiones internas, variables, ...
    wireregintegerrealtime// componentes (submódulos)
    instancias_de_modulos
    instancias_de_primitivas
    // estructura/comportamiento
    assign
    initial
    always
endmodule

Tipos de datos

Conjunto de valores

Verilog consta de sólo cuatro valores básicos. Casi todos los tipos de datos de Verilog almacenan todos estos valores:

Estos dos últimos valores x y z tienen un uso limitado para la síntesis.
La representación numérica de valores en las distintas bases de numeración responde a las siguientes reglas que permite los siguientes caracteres:

Sintaxis valor

  <size>'<base format><number>
TipoPrefijoCaracteres permitidos
binario'b01xXzZ_?
octal'o0-7xXzZ_?
decimal'd0-9_
hexadecimal'h0-9a-fA-FxXzZ_?

Ejemplos

Los tipos de datos que tienen valores asociados como los indicados anteriormente necesitan declararse y responden a la siguiente sintaxis:

Sintaxis definición dato

    <data_type> [<msb>:<lsb>] <list_of_identifiers>;

Después de declarar un vector, puede ser referenciado:

Sintaxis selección de bit o parte de vector

    <variable_name>[index]
    <variable_name>[msb:lsb]

Ejemplos

wire [7:0] result;
reg [0:3] cuenta;
reg [23:16] rega, regb;
reg [7:0] a, b;
reg [3:0] ls;
reg c;
c = a[7] & b[7];         // selecciones de bit
ls = a[7:4] + b[3:0];    // selección de parte de vector

Cable (wire)

Un wire representa un cable/hilo/nodo físico en un circuito y se utiliza para conectar (net) puertas o módulos, así como modelar nodos combinacionales en sentencias de asignación continua. No almacena su valor, pero debe ser generado por una instrucción de asignación continua o por conexión a la salida de una puerta o módulo. Dentro de una función o bloque, puede leerse su valor pero no asignarlo. El tipo por defecto es de 1 bit.

Otros tipos específicos de cables son:

Sintaxis

wire [msb:lsb] wire_variable_list; 
wand [msb:lsb] wand_variable_list; 
wor [msb:lsb] wor_variable_list; 
tri [msb:lsb] tri_variable_list;

Ejemplo

wire c;           // simple wire 
wand d; 
assign d = a;     // el valor de d es el AND lógico de
assign d = b;     // a y b 
wire [9:0] A;     // un cable (vector) de 10 hilos (wires)

Registro (reg)

Este tipo de datos sirven para modelar tanto nodos lógicos combinacionales como secuenciales, aunque normalmente se asocia con aquellos que mantienen su valor hasta la siguiente asignación y aparecen en el lado izquierdo de las expresiones en procedimientos initial y always, o function.

Un reg es el tipo de datos que debe utilizarse para latches, flip-flops y memorias, sin embargo a menudo se sintetiza también como conductor (“combinacional”) en lugar de almacenamiento.

En los registros de varios bits (vectores), los datos se almacenan por defecto como números sin signo y no se realiza ninguna extensión de signo, por lo que deberá tener en cuenta si se necesita operar en complemento a 2.

Sintaxis

reg [msb:lsb] reg_variable_list;

Ejemplo

reg a;            // variable registro de 1-bit
reg [7:0] tom;    // un vector de 8-bit; un banco de 8 registros de 1-bit 
reg [5:0] b, c;   // dos variables de 6-bit

Puerto (input, output, inout)

Estas palabras clave declaran puertos de entrada (input), salida (output) y bidireccionales (inout) de un módulo (module) o tarea (task).

Los puertos input e inout son de tipo wire, los output pueden configurarse de tipo wire, reg, wand, wor o tri. El valor predeterminado es wire.

Sintaxis

input [msb:lsb] input_port_list; 
output [msb:lsb] output_port_list; 
inout [msb:lsb] inout_port_list;

Ejemplo

module ejemplo_puertos (
  input a,            // una entrada, por defecto wire simple 1-bit
  output b, e,        // dos salidas, por defecto wires de 1-bit
  output reg [1:0] c  // una salida de tipo reg de 2-bit
); 

Entero (integer)

Los enteros son variables de propósito general. En síntesis se utilizan principalmente para los índices de bucles, parámetros y constantes. Son implícitamente del tipo reg, sin embargo, almacenan datos como números con signo mientras que los tipos declarados explícitamente como reg se almacenan sin signo.

Si contienen números que no están definidos en tiempo de compilación, su tamaño predeterminado será de 32-bit. Si contienen constantes, el sintetizador las ajusta al ancho mínimo necesario en la compilación.

Sintaxis

integer integer_variable_list; 
... integer_constant ... ;

Ejemplo

integer a;        // entero simple de 32-bit
assign b = 63;    // 63 se considera un entero de 7-bit

También existe un tipo derivado (real) para representar números reales en doble precisión (64-bit) con objeto fundamental de simulación y con conversión con redondeo al entero más cercano en caso de síntesis.

Fuentes (supply0, supply1)

supply0 y supply1 definen cables conectados a lógica 0 (ground) y lógica 1 (power), respectivamente.

Sintaxis

supply0 logic_0_wires; 
supply1 logic_1_wires;

Ejemplo

supply0 my_gnd; // equivalente a un wire asignado a 0 
supply1 a, b;

Tiempo (time)

El tiempo es una cantidad de 64-bit que puede utilizarse en combinación con la tarea de sistema $time para almacenar el tiempo de simulación.

time no está soportado en síntesis y, por lo tanto, se utiliza sólo para fines de simulación.

Sintaxis

time time_variable_list;

Ejemplo

time c; 
c = $time;    // c = tiempo de simulación actual

Parámetro (parameter)

Modelan valores constantes (no nodos), lo que permite p.ej. que las constantes como la longitud de la palabra se definan simbólicamente en un lugar. Esto hace que sea fácil cambiar la longitud de la palabra más tarde, cambiando sólo el parámetro. Una forma alternativa al uso de parámetros es utilizar macros sustituibles en tiempo de preprocesado.

Sintaxis

parameter par_1 = valor, 
          par_2 = valor, .....; 
parameter [rango] parm_3 = valor

Ejemplo

parameter ancho_de_bus = 7;
parameter add = 2’b00, sub = 3’b111; 
parameter n=4; 
parameter [3:0] st4 = 4’b1010; 
. . . 
reg [n-1:0] harry;        /* Un registro de 4-bit cuya longitud 
                             se establece por el parámetro n anterior */ 
always @(x) 
  y = {{(add - sub){x}};  // Operador de repetición 
  if (x) begin 
    state = st4[1]; else state = st4[2]; 
  end

Ejemplos

wire vs reg

Tanto wire como reg permiten modelar nodos combinacionales, si bien deben utilizarse distintos tipos de asignaciones en cada caso, p.ej. si queremos construir un sumador de 8 bits:

sum_wire.v
module sum (
  input [7:0] sum1, sum2,     // por defecto, los puertos son wire
  output [7:0] res
);
  assign res = sum1 + sum2;
endmodule // sum

Un circuito equivalente al anterior se obtendría con la siguiente descripión:

sum_reg.v
module sum (
  input [7:0] sum1, sum2,
  output reg [7:0] res      // ahora es un registro
);
  always @(sum1 or sum2)
    begin
      res = sum1 + sum2;    // esto no se puede hacer con wire
    end
endmodule // sum

Observaciones

parameter, if

Continuando con el ejemplo del sumador anterior, podríamos introducir un parámetro para determinar el ancho de palabra de datos con el que opera, y un nuevo puerto que permita seleccionar la operación a realizar con los datos y ampliar su funcionalidad a sumador/restador:

sum_if.v
module sum 
#(parameter no_bits = 8)
(
  input sel_op,
  input [no_bits-1:0] sum1, sum2,
  output reg [no_bits-1:0] res
);
 
  always @(sel_op or sum1 or sum2) begin
    if (sel_op)
      res = sum1 + sum2;     // asignación procedural
    else
      res = sum1 - sum2;     // asignación procedural
  end
endmodule // sum

Operadores

Aritméticos

Realizan operaciones aritméticas.

+ (suma)
- (resta)
* (multiplicación)
/ (división)
% (resto/módulo)

+ y - pueden ser operadores unarios (-z) o binarios (x-y)

Ejemplo

parameter n = 4; 
reg [3:0] a, c, f, g, count; 
f = a + c; 
g = c - n; 
count = (count + 1) % 16;   // puede contar de 0 a 15

Relacionales

Comparan dos operandos y devuelven un único bit 1 o 0.
Se sintetizan como comparadores.
Los valores de wire y reg se consideran positivos, p.ej. (-3’b001)==3’b111 y (-3d001)>3d110 mientras que los enteros se consideran números con signo, p.ej. -1<6.

< (menor que)
> (mayor que)
<= (menor o igual que)
>= (mayor o igual que)
== (igual a)
!= (no igual a)

Ejemplo

if (x == y) e = 1; /* equivalente: e = (x == y); */
else        e = 0;    
// Comparar en complemento a 2 a>b
reg [3:0] a, b; 
if (a[3] == b[3]) a[2:0] > b[2:0]; 
else              b[3];

Bit-wise

Comparan bit a bit dos operandos.
Notación idéntica a operadores reductores (unarios).

~ (NOT bitwise)
& (AND bitwise)
| (OR bitwise)
^ (XOR bitwise)
^~, ~^ (XNOR bitwise)

Ejemplo

module and2 (
  input [1:0] a, b,
  output [1:0] c
);
  assign c = a & b;
endmodule //and2

Lógicos

Operan de forma similar a los bitwise sólo para operandos de un solo bit, y devuelven un solo bit 1 ó 0.
Pueden trabajar con expresiones, números enteros o grupos de bits y tratan todos los valores distintos de cero como 1.
Normalmente se utilizan en sentencias condicionales (if … else) ya que funcionan con expresiones.

! (NOT lógico)
&& (AND lógico)
|| (OR lógico)
== (igualdad lógica)
!= (desigualdad lógica)
=== (igualdad case)
!== (desigualdad case)

En === y !== se tienen en cuenta bits con valores x o z para la comparación bitwise, en cambio cuando aparecen == y != el resultado de la comparación es siempre x y estos últimos son sintetizables por contra de los primeros.

Ejemplo

wire [7:0] x,y,z;     // variables multibit
reg a;
...
if ((x==y)&&(z)) a=1; // a = 1 si x igual que y, y z no es cero
else a = !x;          // a = 0 si x es algo distinto a cero

Reductores

Operan sobre todos los bits de un vector y devuelven un valor de un solo bit.
Son la versión unaria (un solo argumento) de los operadores bitwise anteriores.

& (AND reductor)
| (OR reductor)
^ (XOR reductor)
~& (NAND reductor)
~| (NOR reductor)
~^, ^~ (XNOR reductor)

Ejemplo

module chk_zero (
  input [2:0] a,
  output z
);
  assign z = ~|a; // NOR reductor
endmodule //chk_zero

Desplazamiento

Desplazan el primer operando el número de bits especificado por el segundo operando.
Las posiciones sobrantes se rellenan con ceros, tanto en desplazamentos a derecha como a izquierda (no hay extensión de signo).

« (desplazamiento a izquierda)
» (desplazamiento a derecha)

Ejemplo

assign c = a << 2; /* c = a deplazado a la izquierda 2 bits;
                      las posiciones vacantes se rellenan con 0’s */

Condicional

Similar al utilizado en C/C++. Se evalúa una de la las dos expresiones basadas en una condición (operador ternario).
Se sintetiza a base de multiplexores.

Sintaxis

conditional_expression ? true_expression : false_expression

Ejemplo

assign a = (inc == 2) ? a+1 : a-1; */ se incrementa o decrementa a si el valor de inc coincide */
assign a = (g) ? x : y;

Concatenación

Combina dos o más operandos para formar un vector mayor.
{ } (concatenación)

Ejemplos

wire [1:0] a, b;   wire [2:0] x;   wire [3:0] y;
assign x = {1’b0, a};                          // x[2]=0,  x[1]=a[1],  x[0]=a[0]
assign y = {a, b};                             /* y[3]=a[1],  y[2]=a[0], y[1]=b[1], y[0]=b[0] */
assign foo = { a[4:3], 1’b0, c[1:0] };
assign foo = { a[4], a[3], 1’b0, c[1], c[0] }; // equivalente a la anterior
assign {cout, sum} = a + b + cin;              // concatenación de un resultado

Repetición

Hace múltiples copias de un item.

{n{item}} (n réplicas de item)

Ejemplos

wire [1:0] a, b;   wire [4:0] x, y;
assign x = {2{1’b0}, a};  // equivalente a x = {0,0,a}
assign y = {2{a}, 3{b}};  // equivalente a y = {a,a,b,b,b}
wire [3:0] z = {4{1’b1}}; // equivalente a z = 4’b1111

Se debe evitar realizar repeticiones nulas ya que ocasionan errores en algunos sintetizadores, p.ej.

parameter n=5, m=5;
assign x = {(n-m){a}}

Precedencia

La siguiente tabla muestra la precedencia de los operadores de mayor a menor. Los operadores del mismo nivel evalúan de izquierda a derecha.
Se recomienda utilizar paréntesis para definir el orden de precedencia y mejorar la legibilidad del código.

OperadorDescripción
[ ]selección de bits ó parte
( )paréntesis
!,~NOT lógico y bit-wise
&, |, ~&, ~|, ^, ~^, ^~reducción AND, OR, NAND, NOR, XOR, XNOR
+,-(unario) signo positivo o negativo
{ }concatenación
{{ }}repetición
*,/,%(binario) multiplicación, división y resto (no siempre sintetizable)
+,-(binario) suma y resta
«,»desplazamientos a izquierda y derecha
<,>,,>=comparaciones
==,!=igualdad y desigualdad
&AND bit-wise
^,^~,~^XOR bit-wise, XNOR bit-wise
|OR bit-wise
&&AND lógico
||OR lógico
? :(ternario) condicional

* Otras consideraciones importantes:

Otros ejemplos

4'hc > 10        (TRUE)
8'd12 && 9'h1    (TRUE)
8'h0 || 0        (FALSE)
!8'h6            (FALSE)
~4'b0111         (4'b1000)
2'b01 & 2'b11    (2'b01)
4'h8 | 4'h1      (4'h9)
&16'h324d        (1'b0)
|16'h1           (1'b1)
8'hfe << 3       (8'hf0)

Módulos

Un módulo es la entidad de diseño principal de Verilog. La primera línea de una declaración de módulo especifica el nombre y la lista de puertos (argumentos), así como el tipo de entrada/salida y ancho de cada puerto (por defecto el ancho es 1-bit).
Las variables del puerto deben ser declaradas como wire, wand, …, reg, aunque por defecto toma el valor wire. Normalmente las entradas suelen ser wire, ya que se registran fuera del módulo, las salidas en cambio suelen ser de tipo reg si se almacenan dentro de bloques always o initial.

Sintaxis

module module_name (
  input [msb:lsb] input_port_list,
  output [msb:lsb] output_port_list,
  inout [msb:lsb] inout_port_list
);
  ... statements ...
endmodule

Ejemplo

small_block.v
module small_block (      // Interfaz del módulo
  input a, b, c,
  output o1, o2
);
   wire s;                // Estructura y comportamiento 
   assign o1 = s | c;     // or
   assign s = a & b;      // and
   assign o2 = s ^ c;     // xor
endmodule

Instanciación de módulos

Las declaraciones de módulo son plantillas a partir de las cuales se crean objetos reales (instanciaciones). Los módulos se instancian dentro de otros módulos, y cada instancia crea un objeto único a partir de la plantilla. La excepción es el módulo de nivel más alto (top) que es su propia instancia.

Sintaxis básica para instanciación

nombre_módulo
  nombre_instancia_1(lista_conexión_puertos), 
  nombre_instancia_2(lista_conexión_puertos),
  ......
  nombre_instancia_n(lista_conexión_puertos);

Argumentos del módulo
Los puertos del módulo instanciado deben coincidir con los definidos en la plantilla. Esto se especifica de dos formas posibles:

Ejemplo

and4.v
// MODULE DEFINITION
module and4(
  input [3:0] a, b,
  output [3:0] c
);
  assign c = a & b;
endmodule //and4
// MODULE INSTANTIATIONS
wire [3:0] in1, in2;
wire [3:0] o1, o2;
/* C1 is an instance of module and4
   C1 ports referenced by position */
and4 C1 (in1, in2, o1);
/* C2 is another instance of and4.
   C2 ports are referenced to the
   declaration by name. */
and4 C2 ( . c(o2), . a(in1), . b(in2));

Los módulos no pueden instanciarse dentro de bloques procedurales.

Módulos parametrizados

Pueden crearse módulos parametrizados y especificar el valor de los parámetros en cada instancia del módulo. Las puertas primitivas tienen parámetros que han sido predefinidos como retrasos.

Sintaxis básica para instanciación

nombre_módulo #(valor_parámetro_1,
                valor_parámetro_2, ...) 
  nombre_instancia(lista_conexión_puertos);

Ejemplos

shift_n.v
// Definición de módulo
module shift_n            // usado en módulo test_shift
#(parameter N = 2)        // el valor por defecto de N es 2
(         
  input [7:0] it, 
  output [7:0] ot
);
  assign ot = (it << N); // se desplaza a la izquierda n veces (bits)
endmodule //shift_n

El módulo anterior podría usarse (instanciarse) y conectar sus puertos en otros módulo, p.ej.:

// Instanciaciones parametrizadas
wire [7:0] in1, ot1, ot2, ot3;
// Conexiones implícitas
shift_n      shft2(in1, ot1); // desplazamiento de 2; por defecto
shift_n #(5) shft5(in1, ot3); // desplazamiento de 5; reeemplaza parámetro 2.
// Conexiones explícitas
shift_n #(.N(3)) shft3(.it(in1), .ot(ot2)); // desplazamiento de 3; reeemplaza parámetro 2.

Otro ejemplo de un contador:

ex_counter.v
module counter (
  input            clk, rst,
  output reg [7:0] count
);
  parameter tpd_clk_to_count = 1;
  parameter tpd_reset_to_count = 1;
  //...
endmodule // counter
ex_test_counter.v
module test_counter;
  reg clk_t, rst_t;
  wire [7:0] count_t;
 
  // conexión implícita:
  counter  #(5,10)  dut(clk_t, rst_t, count_t);
 
  /* conexión explícita:
  counter  #(5,10)  dut(.count(count_t),
                        .clk(clk_t),
                        .rst(rst_t));
  */
  //...
endmodule // test_counter

Sintaxis alternativa para instanciación

defparm nombre_instancia.parámetro = valor_parámetro;
nombre_módulo nombre_instancia(lista_conexión_puertos);

Ejemplo

// Instanciaciones parametrizadas
wire [7:0] in1, ot1, ot2, ot3;
defparm shft3.N=3, shift5.N=5;
shift_n shft2(in1, ot1), // desplazamiento de 2; por defecto
shift_n shft3(in1, ot2); // desplazamiento de 3; reeemplaza parámetro 2.
shift_n shft5(in1, ot3); // desplazamiento de 5; reeemplaza parámetro 2.

No todos los sintetizadores permiten la palabra clave defparam como forma alternativa de cambio de parámetros.

Uso de macros como parámetros
Las macros hacen sustituciones de cadenas y pueden funcionar como parámetros, especialmente útiles para parámetros sin necesidad de pasar por los módulos.

Ejemplo

// Uso de macros como parámetros
`define M 8      // ancho de palabra
module top
  wire [`M-1:0] x, y, z; // equivalente a: wire [8-1:0] x,y,z;

Modelado comportamental

El estilo de descripción HDL comportamental o procedural se utiliza para modelar un diseño en el más alto nivel de abstracción de entre los posibles con Verilog HDL. Proporciona gran potencia para diseños complejos, sin embargo, pequeños cambios en los métodos de codificación implican grandes cambios en el hardware generado.
Las sentencias procedurales sólo pueden usarse en procedimientos asociados a bloques always e initial.

Asignaciones

Tipo de sentenciaDestino (LHS)
Asignación continua net (vector o escalar)
selección de bit constante de una net vector
selección de una parte constante de una net vector
concatenación de cualquiera de la 3 anteriores
Asignación procedural registro (vector o escalar)
selección de bit constante de un registro vector
selección de una parte constante de un registro vector
elemento de memoria
concatenación de cualquiera de la 4 anteriores

Asignación continua

    assign sal = a & b;
    assign c = a & b,
           d = a | b,
           e = a ^ b;

Bloques begin ... end

begin : nombre_bloque
reg [msb:lsb] lista_variables_reg;
integer [msb:lsb] lista_enteros;
parameter [msb:lsb] lista_parametros;
... sentencias ...
end
named.v
     always @(data)
       begin:named_block
         integer i;
         parity = 0;
         for(i=0; i<11; i=i+1)
           parity = parity ^ data[i];
       end // named_block

Sentencias if ... else

Las sentencias if … else ejecutan una sentencia o bloque de sentencias dependiendo del resultado de la expresión que sigue al if. Si las expresiones condicionales en todos los if se evaluan como falsas, entonces se ejecutan, si están presentes, las sentencias del bloque else.

Pueden incluir tantas sentencias intermedias else if como se requieran, pero sólo puede habr un solo bloque if y un solo bloque else. Si sólo hay una sentencia en un bloque, las sentencias begin … end pueden omitirse.

Tanto las sentencias else if como else son opcionales. Sin embargo, si todas las posibilidades no están específicamene cubiertas, la síntesis generará latches extra, que no son deseables en la mayoría de los casos.

if (expresion)
begin
... sentencias ...
end
else if (expresion)
begin
... sentencias ...
end
... más bloques else if ...
else
begin
... sentencias ...
end
if_alu.v
if (alu_func == 2’b00)
  aluout = a + b;
else if (alu_func == 2’b01)
  aluout = a - b;
else if (alu_func == 2’b10)
  aluout = a & b;
else // alu_func == 2’b11
  aluout = a | b;
if_latch.v
if (a == b)    // Este if sin else generará 
  begin        // latches para 'x' y 'ot' que
    x = 1;     // almacenarán sus valores antiguos si (a != b).
    ot = 4’b1111;
  end
if_sel.v
always @(intreg or selector)
  begin
    if (selector == 2’b00)
      result_int = intreg[0];
    else if (selector == 2’b01)
      result_int = intreg[1];
    else if (selector == 2’b10)
      result_int = intreg[2];
    else if (selector == 2’b11)
      result_int = intreg[3];
  end

Sentencias case

La sentencia case genera una ramificación multicamino basada en la comparación de la expresion con una lista de opciones.

Las sentencias enel bloque default se ejecutará cuando ninguna de las comparaciones con las opciones se evalue como cierta. Sin defult, si no hay comparaciones ciertas, la síntesis generará latches no desebles en la mayoría de los casos. Las buenas prácticas de diseño invitaan al hábito de poner la sentencia default tanto si es necesaria como si no.

Si los valores que se asignan (RHS) en la clausula default son indeterminados o irrelevantes, se definen como 'X' para que el proceso de minimización lógica los trate como irrelevantes y se ahorre cierta área en el circuito final.

Las opciones del case pueden ser una simple constante, una expresión, o una lista separada por comas de las mismas.

case (expresion)
  case_opcion1:
    begin
    ... sentencias ...
    end
  case_opcion2:
    begin
    ... sentencias ...
    end
  ... más bloques de opciones case ...
  default:
    begin
    ... sentencias ...
    end
endcase

Ejemplos:

case_alu1.v
case (alu_ctr)
  2’b00: aluout = a + b;
  2’b01: aluout = a - b;
  2’b10: aluout = a & b;
  default: aluout = 1’bx; // Tratado como irrelevante
endcase                   // mínima generación de lógica
case_alu2.v
case ({w, y})
  2’b00: aluout = a + b; //si 'w' e 'y' is 2’b00.
  2’b01: aluout = a - b;
  2’b10: aluout = a & b;
  default: $display(“Valores no válidos w,y = %b %b ”, w, y);
endcase    // muestra un error si w,y son 11, o contienen ‘x’s.

casex

La sentencia casex sirve para expresar indiferencias en las ramas del case.

En casex(a) las opciones de la constante “a” pueden contener Z, X o ? que pueden utilizarse como irrelevantes para la comparación. Con case la correspondiente variable de simulación tendría que corresponder con un triestado, desconocido u otra señal. En definitiva, 'case' usa X para comparar con una señal desconocida, mientras que casex lo trata como irrelevante para reducir lógica. La sintáxis es similar a la de case.

casex_sel.v
input [2:0] sel;
casex (sel)
  3’b10x: ...
  3’bx10: ...
  3’bx11: ...
  3’b00x: ...
  default: ...
endcase
casex (a)
  2’b1x: msb = 1; // msb = 1 si a = 10 o a = 11
    // si fuese case(a) sólo coincidiría cuando a=1x
  default: msb = 0;
endcase

casez

casez es similar a casex excepto que sólo pueden usarse ? o Z (no X) en las opciones de caso constantes como irrelevantes. Su uso favorece la simulación, en el caso que aparezca una señal x, que no coincidirá con un 0 o 1 en las opciones del caso. La sintáxis es similar a la de case.

casez_sel.v
casez (d)
  3’b1??: b = 2’b11; // b = 11 si d = 100 o superior
  3’b01?: b = 2’b10; // b = 10 si d = 010 o 011
  default: b = 2’b00;
endcase

Bloques always

    always @(<eventos>)
      begin
        // algoritmo de procesamiento
      end
    always @(a or b or c or d) // sensible a: a, b, c, d
    always @(posedge clk) // sensible a flanco + de clk
 
    always @(negedge clk) // sensible a flanco -de clk
ffd.v
    // flip-flop
    always@(posedge clk)
      begin
        q = d;
      end
mux2.v
    // multiplexor
    always @(sel or ent1 or ent2)
      begin
        if(!sel)
          sal = ent1;
        else
          sal = ent2;
      end
ffdr.v
    // flip-flop con reset asíncrono
    always @(posedge clk or negedge rstn)
      begin
        if(!rstn)
          q = 1’b0;
        else
          q = d;
      end
ffdrs.v
    // flip-flop con reset síncrono
    always @(posedge clk)
      begin
        if(!rstn)
          q = 1’b0;
        else
          q = d;
      end
blocking.v
    always @(a)   // a pasa a valer 1
      begin
        b = a;    // b vale 1
        c = b;    // c vale 1
      end
non_blocking.v
    always @(a)   // a pasa a valer 1
      begin
        b <= a;   // b valdrá 1 al salir
        c <= b;   // c valdrá 0 al salir
      end
exif.v
    always @(posedge clk)
      begin
        if(ce)
          begin
            rega = ent1;
            regb = ent2;
          end
      end
excase.v
     always @(intreg or selector)
       case(selector)
         2’b00: result_int = intreg[0];
         2’b01: result_int = intreg[1];
         2’b10: result_int = intreg[2];
         2’b11: result_int = intreg[3];
       endcase
asram.v
module asram (
  input [3:0] address,
  input [7:0] data_in,
  output reg [7:0] data_out
);
  reg [7:0] mem[3:0];
 
  always @(address or data_in or we)
    if(we)
      mem[address] = data_in;
    else
      data_out = mem[address];
mux_case.v
    module mux_case (source, ce, wrclk, selector, result);
      input [3:0] source;
      input ce, wrclk;
      input [1:0] selector;
      output result;
      reg [3:0] intreg; // intreg y result_int son señales locales
      reg result, result_int;
 
      always @(posedge wrclk)
        begin
          if(ce)
            intreg = source;
          result = result_int;
        end
 
      always @(intreg or selector)
        case(selector)
          2’b00: result_int = intreg[0];
          2’b01: result_int = intreg[1];
          2’b10: result_int = intreg[2];
          2’b11: result_int = intreg[3];
        endcase
    endmodule // mux_case

Bucles

En Verilog hay bucles for , while , repeat e indefinidos (forever), poco recomendables para síntesis.

for

Similar a los bucles for en C/C++, se utilizan para ejecutar repetidamente una sentencia o bloque de sentencias.

Si el bucle contiene sólo una sentencia pueden omitirse las palabras clave begin … end.

  for (count = valor1;
       count </<=/>/>= valor2;
       count = count +/- step)
    begin
    ... sentencias ...
    end
ex_for.v
integer i;
input clk;
reg [4:0] input_signal, result;
reg enable;
always @(posedge clk)
  for (i=0; i<5; i=i+1)
    result[i] = enable & input_signal[i];

while

El bucle while ejecuta repetidamente una sentencia o bloque de sentencias hasta que la expresión condicional se evalua como falsa.

Para evitar realimentación combinacional en síntesis, un bucle de este tipo debe poder romperse con una sentencia del tipo @(posedge/negedge clock).

En simulación basta con incluir un retardo dentro del bucle.

Si el bucle contiene sólo una sentencia pueden omitirse las palabras clave begin … end.

while (expresion)
begin
... sentencias ...
end
while (!overflow) begin
  @(posedge clk);
  a = a + 1;
end

forever

La sentencia forever ejecuta un bucle inifinito de una sentencia o bloque de sentencias.

Para evitar realimentación combinacional en síntesis, un bucle de este tipo debe poder romperse con una sentencia del tipo @(posedge/negedge clock).

En simulación basta con incluir un retardo dentro del bucle.

Si el bucle contiene sólo una sentencia pueden omitirse las palabras clave begin … end.

forever
begin
... sentencias ...
end
forever begin
  @(posedge clk);  // o p.ej.: a = #9 a+1;
  a = a + 1;
end

repeat

La sentencia repeat ejecuta una sentencia o bloque de sentencias un número fijo de veces.

repeat (numero_veces)
begin
... sentencias ...
end
repeat (2) begin  // después de 50,  a = 00,
  #50 a = 2’b00;  // después de 100, a = 01,
  #50 a = 2’b01;  // después de 150, a = 00,
end               // después de 200, a = 01
ex_repeat.v
always @(i)
  begin
    o = i;
    repeat (4'b1011)
      o = ~o;  // o = ~i
  end

disable

La ejecución de una sentencia disable termina un bloque y pasa el control a la siguiente sentencia después de ese bloque.

Es como una sentencia break en C, excepto que puede terminar cualquier bucle, no sólo en el que aparece.

* Sintáxis:

disable nombre_bloque;
ex_forever.v
always
  begin
    @(posedge clk) out = 0;
    begin: for_ever
      forever
        begin: name
          @(posedge clk)
          if (reset) disable for_ever;
            out = out + 1;
        end
    end
  end

Funciones y tareas

En Verilog se pueden definir funciones y tareas (task), que son procedimientos.

assign y = func(a,b); // Las funciones son valores
my_task(a, b, c, d);  // Las tareas son sentencias.

Las funciones y tareas se definen dentro de los módulos (dentro de module-endmodule) con las palabras reservadas function-endfunctiony task-endtask respectivamente de modo similar a los módulos.
Su uso principal es para simulación y creación de testbenches. No es aconsejable utilizarlas para síntesis salvo que sus límites o rango sean fijos tanto en datos como en iteraciones de bucles.

El valor de retorno de una función se puede utilizar directamente en una expresión sin asignarla antes a una variable de tipo registro o cable, utilizando la llamada de función como uno de los operandos, teniendo en cuenta el ancho en bits de dicho valor.

Ejemplos

// Una función de utilidad para calcular el logaritmo en base2
function integer clogb2;
 input [31:0] value;
 for (clogb2=0; value>0; clogb2=clogb2+1)
   value = value>>1;
endfunction //clogb2
ex_func_mult.v
// Multiplicador secuencial suma-desplazamiento
function [15:0] mult;
  input [7:0] a, b;
  reg [15:0]  r;
  integer     i;
begin
  if (a[0] == 1)
    r = b;
  else
    r = 0;
  for (i=1; i<7; i=i+1)
  begin
    if (a[i] == 1)
      r = r + b << 1;
  end
  mult = r;
end
endfunction
ex_task_osc.v
// Oscilador !! no síntesis
task T;
  inout io;
  output o;
begin
  o = io;
  io = ~io;
end
endtask

Síntesis Lógica RTL

Síntesis de circuitos combinacionales

Hay 2 formas de describir circuitos combinacionales:

Por ejemplo, un multiplexor:

assign sal = (sel) ? ent1 : ent0;

o:

always @(sel or ent0 or ent1)
  if (sel) sal = ent1;
  else sal = ent0;

El uso de asignaciones continuas es recomendable cuando se trata de un combinacional sencillo del que se conoce la ecuación, una operación aritmética simple o una asignación condicional en la que se puede compactar la descripción.

Hay que recordar que las asignaciones de este tipo se realizan sobre wires. Por lo demás, no hay que tomar “precauciones especiales”.

Ejemplos:

assign a = b + c;
assign sal = (a & b) | (c & d);
assign sal = (sel) ? (a + b) : (a – b);
assign sal = ena_i & sal_i,
       saln = ena_i & (!sal_i),
       ena = ena_i;

El uso de bloques always está indicado cuando el combinacional tiene una cierta complejidad.

Hay que tener en cuenta lo siguiente:

ex_decod.v
wire [3:0] dato_in;
wire enable;
reg [1:0] dato_out;
 
always @(enable or dato_in)
if (enable)
  casex (dato_in)
    4'bxxx1: dato_out = 2'b00;
    4'bxx10: dato_out = 2'b01;
    4'bx100: dato_out = 2'b10;
    4'b1000: dato_out = 2'b11;
  endcase
else // si no lo ponemos => latch
  dato_out = 2'b00;

Para expresar condiciones de indiferencia en las salidas, se utiliza el valor “X”.

always @(ent)
  casex (ent)
    4'bxx01: sal = 4'b1110;
    4'bx010: sal = 4'b1010;
    default: sal = 4'hx;
  endcase

En las sentencias case es importante incluir la rama default cuando no se definen todas las combinaciones de entrada.

always @(ent)
  casex (ent)
    4'bxx01: sal = 4'bxx10;
    4'bx010: sal = 4'b10xx;
    default: sal = 4'b0000;
  endcase

Intentar simplificar la asignación de valores a salidas puede llevar a cometer errores.

always @(ent)
  casex (ent)
    4'bxx01: sal = 4'b0010;
    4'bx010: sal = 4'b1000;
    default: sal = 4'b0000;
  endcase

¡Ojo!No es equivalente a esta descripción:

always @(ent)
  casex (ent)
    4'bxx01: sal[1:0] = 2'b10;
    4'bx010: sal[3:2] = 2'b10;
    default: sal = 4'b0000;
  endcase // LATCH!

Hay una técnica para evitar este tipo de errores (poco utilizada):

always @(ent) begin
  sal = 4'b0000;
  casex (ent)
    4'bxx01: sal[1:0] = 2'b10;
    4'bx010: sal[3:2] = 2'b10;
    default: sal = 4'b0000;
  endcase
end

En la descripción de circuitos aritméticos, el lenguaje considera que el bit más significativo de los objetos es el primero que aparece en la definición del rango.

reg [3:0]  a;
reg [0:3]  b;
wire[3:0]  c;
// si a vale 4'b0001 y b vale 4'b1100
assign c= a + b; // c valdrá 4'hd

Es muy aconsejable definir siempre los rangos con el mismo criterio (preferiblemente descendentes).

Además, Verilog considera que los vectores representan números binarios. Esto puede corregirse con la directiva ‘signed; afecta a las operaciones de comparación.

// a = 4'b1000; b = 4'b0001;
c = a > b;  // c vale 1
c = 'signed a > 'signed b; // c vale 0

En ocasiones hay que describir buffer tri-estado. Se hace de la siguiente manera:

assign sal = (enable) ? sal_i : 1'hz;

Si se trata de un pin bidireccional:

inout pin_bidir;  // pin bidireccional (wire)
wire  ent_bidir;  // el nodo interno de entrada
reg   sal_i;      // el nodo interno de salida
 
assign pin_bidir = (enable) ? sal_i : 1'hz;
assign ent_bidir = pin_bidir;

Aritmética

A la hora de codificar el hardware hay que prever cómo va a ser sintetizado. Un detalle importante que hay que tener presente es si el sintetizador que se va a utilizar es capaz de detectar recursos compartidos. Por ejemplo:

s1 = a + b - c;
s2 = d - c + b;
//Esto se puede hacer con dos sumadores y un restador

Lo mejor es codificar para que el resultado no dependa de las peculiaridades del sintetizador.

ex_sum4_1.v
module sum4 (a, b, c, d, e, sum1, sum2, sum3);
  input  [31:0]   a, b, c, d, e;
  output [31:0]   sum1, sum2, sum3;
 
  assign sum1 = a + b + c;
  assign sum2 = a + b + d;
  assign sum3 = a + b + e;
  // Genera 6 sumadores
endmodule
ex_sum4_2.v
module sum4 (a, b, c, d, e, sum1, sum2, sum3);
  input  [31:0]   a, b, c, d, e;
  output [31:0]   sum1, sum2, sum3;
  wire   [31:0]   a_plus_b;
 
  assign a_plus_b = a + b;
  assign sum1 = a_plus_b + c;
  assign sum2 = a_plus_b + d;
  assign sum3 = a_plus_b + e;
  // Genera 4 sumadores
endmodule

La forma de codificar también puede repercutir en la velocidad de operación.

Síntesis de circuitos secuenciales

Los circuitos secuenciales se modelan siempre utilizando bloques always sensibles al flanco de reloj y, si es necesario, a una señal asíncrona de inicialización o reset.

always @(posedge clk) // flanco positivo
always @(negedge clk) // flanco negativo

El modo que se muestra es el más utilizado para describir reset asíncronos, aunque resulte “algo chocante”.

always @(posedge clk or posedge reset) // reset alto
always @(posedge clk or negedge reset) // reset bajo

Si el circuito tiene reset asíncrono, primero se establece la operación del reset y luego, alternativamente, la operación síncrona del circuito.

always @(posedge clk or posedge reset) // reset alto
  begin
    if (reset)
      // operación de reset
    else
      // operación síncrona
  end

La condición que consulta el nivel de la señal de reset, la “transforma” en señal activa “por nivel”, aunque el bloque sea sensible a su flanco, porque “elimina” el funcionamiento síncrono cuando está activa.

Ejemplo: Circuito síncrono sin reset

ex_dffe.v
module dffe(clk, a, b, c, sel);
  input a, b, sel, clk;
  output c;
 
  reg c;
 
always @(posedge clk)
  if (sel)
    c = a;
  else
    c = b;
endmodule

Ejemplo: Circuito síncrono con reset asíncrono y clock-enable

ex_dffr.v
module dffr
(input clk, reset, a, ce,
 output reg c);
 
always @(posedge clk or negedge reset)
  if (!reset) c = 0;
  else if (ce) c = a;
 
endmodule // dffr

Ejemplo: Circuito síncrono con reset asíncrono y síncrono

ex_dffrs.v
module dffrs
(input clk, reset, reset_syn, a, ce
 output reg c);
 
always @(posedge clk or negedge reset)
if (!reset) c = 0;
else begin
  if (reset_syn) // reset síncrono activo a nivel alto
    c = 0;
  else if (ce)
    c = a;
end
 
endmodule // dffrs

Al igual que en el caso de los circuitos combinacionales, la descripción de la mayor parte de los secuenciales puede realizarse combinando sentencias case, if y operadores aritméticos y lógicos.

En la descripción de circuitos secuenciales hay que tener cuidado con las asignaciones blocking y non-blocking. En general, es recomendable que todas sean non-blocking (<=).

Síntesis de Autómatas

Para describir autómatas (una de las tareas que más frecuentemente hay que abordar durante el desarrollo de un sistema digital), se pueden utilizar hasta tres estilos distintos:

El primero, que por lo demás es el más frecuentemente utilizado, sólo sirve para autómatas de Moore o Mealy con las salidas registradas. El tercero viene en todos los manuales de estilo de los sintetizadores, pero nadie (que yo conozca) lo utiliza. El segundo es apropiado si (es raro) se quiere desarrollar un autómata de Mealy (puro y duro) o se está implementando uno de Moore a partir del dibujo del diagrama de estados.

Desde el punto de vista metodológico, quizás el segundo sea el mejor, pero es un poco incómodo de utilizar si se va desarrollando el autómata al tiempo que se escribe el código, de modo que el más utilizado es el primero.

En el primer método, con un único bloque always, se describe, mediante una sentencia case, donde cada rama es un estado, las transiciones y el valor de las salidas en cada estado. La estructura genérica de este tipo de autómatas es:

always @(posedge clk or negedge reset)
  if (!reset) begin
    estado <= estado_inicial;
    salidas <= valor_estado_inicial;
  end
  else
    case (estado)
      s0: case (entradas)
        e1: estado <= estado_i;
            salidas <= salidas_i;
        e2: ...
      s1: case (entradas)
        ...
      sn: ...

Ejemplo: Autómata simple con variable estado igual a las salidas

fsm1.v
module fsm1
(input clk, reset, c,
 output reg [1:0] status);
 
always @(posedge clk or negedge reset)
  if (!reset) status = 0;
  else
    case (status)
      0: if (c) status = 1;
      1: status = 2;
      2: if (c) status = 1;
         else status = 0;
    endcase
endmodule // fsm1

Ejemplo: Autómata de Mealy con salidas registradas

fsm2.v
module fsm2
(input clk, reset, c,
 output reg [3:0] salida);
 
  reg [1:0] status;  // estado
 
always @(posedge clk or negedge reset)
  if (!reset) begin
    status <= 0;
    salida <= 3'b010;
  end
  else
    case (status)
      0: if (c) begin
           status <= 1;
           salida <= 3'b100;
         end
      1: begin
           status <= 2;
           salida <= 3'b111;
         end
      2: if (c) begin
           status <= 1;
           salida <= 3'b100;
         end
         else begin
            status <= 0;
            salida <= 3'b010;
         end
    endcase
endmodule // fsm2

Ejemplo: Autómata de Moore con dos bloques always

fsm3.v
module fsm3
(input clk, reset, c,
 output reg [3:0] salida);
 
  reg [1:0] status;  // estado
 
// bloque secuencial
always @(posedge clk or negedge reset)
  if (!reset) status <= 0;
  else case (status)
    0: if (c) status <= 1;
    1: status <= 2;
    2: if (c) status <= 1;
  endcase
 
// bloque combinacional
always @(status)
  case (status)
    0: salida <= 3'b010;
    1: salida <= 3'b100;
    2: salida <= 3'b111;
    default: salida <= 3'bxxx;
  endcase
 
endmodule // fsm3

Si fuera de Mealy, con las salidas sin registrar, el segundo proceso sería sensible al estado y a las entradas.

En el diseño de autómatas hay que tener en cuenta algunas consideraciones adicionales:

Módulos estructurales

Los módulos estructurales Verilog son muy fáciles de elaborar con copy&paste a partir de la declaración de los módulos insertados, tomando una precaución:

module control (input ent_ctrl, output sal_ctrl);
...
module controlado (input sal_ctrl, input sal_ctrldo);
...
module top (input ent_ctrl, output sal_ctrldo);
...
  wire sal_ctrl;
 
  // instanciación
  control     U0 (ent_ctrl, sal_ctrl);
  controlado  U1 (sal_ctrl, sal_ctrldo);

Si el módulo tiene definidos parámetros, se puede particularizar su valor para cada instancia: entre paréntesis con #, o dejar que tomen el valor de la declaración si no se pone nada.

module sumador
#(parameter SIZE = 4)
 (input [SIZE-1:0] a, b);
...
 
module top (...);
...
wire [7:0] a, b, sal;
...
// instanciación
sumador  #(8)  U0 (a, b, sal);
...

Síntesis de memorias

Las memorias se modelan en Verilog como arrays de 2 dimensiones mediante variables tipo registros a las que puede acceder por palabras. Si se desea obtener uno o varios bits concretos debe asignarse la salida a un registro o cable y seleccionar la parte deseada de esta nueva variable.

Sintaxis

reg [wordsize:0] array [0:arraysize]

Ejemplo

reg [7:0] mem [0:255];

En Verilog no hay arrays multi-dimensionales, ni es posible definir ni conectar puertos de tipo arrays, estas facilidades están incluidas en SystemVerilog.

Inicialización de memorias

Las memorias pueden inicializarse de forma secuencial, p.ej.:

reg [15:0] mem[0:255];
integer i;
initial
  for (i=0; i<256; i=i+1) mem[i] = 0;
  // initial each location with 0

O a partir del contenido de ficheros ASCII, que definen su contenido numérico en base binaria o hexadecimal, mediante system tasks $readmemb y $readmemh respectivamente.
El archivo de datos consta de direcciones y datos. Una dirección escrita en hexadecimal como @hhh… indica la dirección de la primera palabra en un bloque de datos. A continuación aparecerán las palabras binarias de datos separadas por espacios en blanco. Los bits binarios legales son 0 1 x X z Z _. Los datos no incluidos en el fichero tomarán valores xxx…. Los datos pueden ubicarse en bloques no contiguos si una dirección precede a cada bloque. Si no se especifica ninguna dirección al comienzo del archivo, se asume @000 para la primera palabra de datos. Los comentarios también se permiten en los archivos de datos.

Sintaxis

readmemb("file_name", array_name);
readmemb("file_name", array_name, start_addr);
readmemb("file_name", array_name, start_addr, finish_addr);
readmemh("file_name", array_name);
// start_addr and finish_addr son opcionales

Si se le da start_addr, la matriz de memoria se llenará comenzando en esa dirección y continuará hasta finish_addr (o final de la matriz). Debe tener start_addr < @hhh…, la dirección inicial en el archivo.

Ejemplo

reg [7:0] memry [0:31]; // memoria de 32 bytes
wire [7:0] memwrd;
wire x;
initial begin
  // Inicializa el contenido de la memoria desde archivo
  $readmemb("init.dat", memry, 8);
  // las palabras 8 y 9 no están en el archivo y por defecto serán x
end
 
// Ejemplos de operaciones tras la inicilaización:
// Extraer la última palabra en memoria
assign memwrd= memry[31];
// Extraer el bit más significativo en la palabra 31
assign x= memwrd[7];
------------------------------- file init.dat-------------------------------
// Como start_addr=8 en memry[0:9] se almacenarán xxxxxxxx
@00A //
10101100 11110000 1x000x11 11110101 01011010 01001100
XxxxZzzz 00000000
@01E // 5'h1E = 5'd30, los guiones bajos dan mejor legilibilidad
1100_1010 00011_0001

ROM

Ejemplo

rom_32x4.v
module rom_32x4
(input [4:0] addr,
 output reg [3:0] dout);
 
always @(addr)
case (addr)
 0:dout = 4'b1110;
 1:dout = 4'b0100;
 2:dout = 4'b1110;
 3:dout = 4'b1001;
 4:dout = 4'b1111;
 5:dout = 4'b0011;
 6:dout = 4'b1000;
 7:dout = 4'b0001;
 8:dout = 4'b0110;
 9:dout = 4'b0001;
 10:dout = 4'b1100;
 11:dout = 4'b0000;
 12:dout = 4'b0110;
 13:dout = 4'b0000;
 14:dout = 4'b0100;
 15:dout = 4'b0110;
 16:dout = 4'b1110;
 17:dout = 4'b0100;
 18:dout = 4'b1110;
 19:dout = 4'b1001;
 20:dout = 4'b1111;
 21:dout = 4'b0011;
 22:dout = 4'b1000;
 23:dout = 4'b0001;
 24:dout = 4'b0110;
 25:dout = 4'b0001;
 26:dout = 4'b1100;
 27:dout = 4'b0000;
 28:dout = 4'b0110;
 29:dout = 4'b0000;
 30:dout = 4'b0100;
 31:dout = 4'b1111;
endcase
endmodule // rom_32x4

RAM

Ejemplo de memoria SRAM-1P

sram1p.v
module sync_ram_singleport
#(parameter addr_width = 8,
  parameter data_width = 8)
 (input clk, we,
  input [addr_width - 1:0] addr,
  input [data_width - 1:0] data_in,
  output[data_width - 1:0] data_out);
 
  reg [addr_width - 1:0] addri;
  reg [data_width - 1:0] mem [(32’b1<<addr_width):0];
 
  always @(posedge clk) begin
    if (we) mem[addr] = data_in;
    addri = addr;
  end
  assign data_out = mem[addri];
 
endmodule //sram1p

Ejemplo de memoria SRAM-2P

sram2p.v
module sync_ram_dualport
#(parameter data_width = 16,
  parameter addr_width = 32)
 (input clk_in, clk_out, we,
  input [addr_width - 1:0] addr_in,
  input [addr_width - 1:0] addr_out,
  input [data_width - 1:0] data_in,
  output reg [data_width - 1:0] data_out);
 
  reg [data_width - 1:0] mem [2^(addr_width -1):0];
 
  always @(posedge clk_in) begin
    if (we)mem[addr_in] <= data_in;
  end
 
  always @(posedge clk_out) begin
    data_out <= mem[addr_out];
  end
 
endmodule //sram2p

Generación de bloques

La generación de bloques es una facilidad del lenguaje para la generación de instanciaciones en bucle. Se utilizan para ello las palabras clave generate y endgenerate que acotan el ámbito de generación apoyados en alguna variable declarada mediante la palabra clave genvar.
Las instanciaciones generadas pueden ser de uno o más de los siguientes tipos:

Existen 3 métodos para crear declaraciones de generar:

Ejemplos

// a  bit-wise  XOR  of  two  N-bit  bases
module bitwise_xor
#(parameter  N=32) //bus width
( output [N-1:0] out,
  input  [N-1:0] i0, i1
);
  genvar  j;       // temp loop variable; does not exist during simulation
  generate  
    for (j=0;j<N;j=j+1) begin :xor_loop
      xor  g1(out[j],i0[j],i1[j]);
    end  //xor_loop
  endgenerate  
 
// alternative method
/*
  reg  [N-1 :0] out;
  generate for (j=0;j<N;j=j+1) begin :bit
    always @(i0[j] or i1[j])    
      out [j]=i0[j]^i1[j];
  end
  endgenerate     
*/
endmodule //bitwise_xor

Simulación digital

Retardos

a = #5 b;   // Equivalente a:
begin
  temp = b;
  #5 a = temp;
end
a = @(posedge clk) b;   // Equivalente a:
begin
  temp = b;
  @(posedge clk) a = temp;
end
a = repeat (3) @(posedge clk) b;  // Equivalente a:
begin
  temp = b;
  @(posedge clk);
  @(posedge clk);
  @(posedge clk)  a = temp;
end

TestBench

Para simular los modelos hardware Verilog hay que ejecutar, en un simulador Verilog, un banco de pruebas o TestBench, es un módulo donde se instancia el modelo que se desea probar (diseño o unidad bajo test) y se le aplican una serie de vectores de test para poder analizar su comportamiento y comprobar su correcto funcionamiento.
Se invocan además una serie de tareas del sistema para visualizar/formatear los resultados de la simulación.

Sintaxis

module test_bench;
...
// Declaración de estímulos
...
// Declaración de salidas
...
// Instanciación del modelo, conectado a
// estímulos y salidas
...
// Definición de estímulos
...
endmodule

Ejemplo

ffd_tb.v
module ffd_tb;
reg clk, reset, a, ce;                     // Estímulos
wire c;                                    // Salida
 
dffr  dut (clk, reset, a, ce, c);          // Modelo bajo pruebas
 
always #100  clk = ~clk;                   // Reloj
 
initial begin
// Inicialización
  reset = 1'b1;
  clk = 1'b0;
  a = 1'b0;
  ce = 1'b0;
  @(negedge clk) reset = 1'b0;              // reset
  @(negedge clk) reset = 1'b1;
  @(negedge clk)
     ce = 1'b1;                             // pongo el ce
     a = 1'b1;
  @(negedge clk);
  @(posedge clk) #10 ce = 1'b0;             // cambio el dato
  @(posedge clk) #1000 $finish;             // se acabó
end
 
endmodule // ffd_tb

Directivas del compilador

`timescale unidad_de_tiempo_de_referencia / precisión

Ejemplo Uso de `timescale

ffd.v
`timescale 100ns/1ns
module ffd
(input clk, reset, d,
 output reg q);
 
always @(posedge clk or negedge reset)
  if (reset==0) q <= 0;
  else q <= d;
 
endmodule //ffd

`define macro_de_texto [valor] `include path/archivo

Ejemplo Uso de `define e `include

// Autómata de control lectura/escritura
// (Memory Manager)
 
// status
`define ST0  2
`define ini0 2'b00
`define esc1 2'b01
`define lec1 2'b10
 
// salidas
`define ackfrommgr_activo   1'b1
`define ackfrommgr_inactivo 1'b1
`define ce_activo           1'b1
`define ce_inactivo         1'b0
`define readext             1'b1
`define writeext            1'b0
`define enack_activo        1'b1
`define enack_inactivo      1'b0
`define enreg_activo        1'b1
`define enreg_inactivo      1'b0
...
`include "../DEFINES/defs_mem_mgr.v"
...
case (status)
  `ini0: begin
     en_ack <= `enack_inactivo;
     canal_dec_reg <= canal_dec;
     canal_reg <= canal;
     if (canal_dec == `canal_ninguno)
       status <= `ini0;
     else if (canal[`log2NC-1]==`ST0)
       status <= `lec1;
     else status <= `esc1;
    end
 `esc1: begin
    status <= `ini0;
    ce <= `ce_inactivo;
    en_ack <= `enack_activo;
   end
 `lec1: begin
   status <= `ini0;
   ce <= `ce_inactivo;
   en_reg <= `enreg_inactivo;
   en_ack <= `enack_activo;
  end
endcase

`ifdef macro_de_texto

 ........................

`else

 ........................

`endif

Ejemplo Uso de ifdef

`ifdef dimm4
  $display("INFO(TEST): Using DIMM4 (4 banks, 9 b/col, 12 b/row)");
`else
  $display("INFO(TEST): Using DIMM2 (2 banks, 8 b/col, 11 b/row)");
`endif

Tareas del sistema

Las “System Tasks” permiten realizar funciones complementarias para la simulación como visualización o volcado de valores de salida por la consola del simulador o a archivos.
Las tareas que extraen datos, como $monitor necesitan estar en bloques initial o always.

$display, $strobe, $monitor

Estos comandos tienen la misma sintaxis y muestran texto en la pantalla durante la simulación. $display y $strobe muestran salida cada vez que se ejecutan, mientras que $monitor aparece cada vez que cambia uno de sus parámetros. La diferencia entre $display y $strobe es que $strobe muestra los parámetros al final de la unidad de tiempo de simulación actual en lugar de exactamente donde se ejecuta.
La cadena de formato es similar a C/C++: %h (hexadecimal), %b (binario), %c (carácter), %s (cadena) y %t (tiempo), %m (nivel de jerarquía). %5d, %5b, etc. darían exactamente 5 espacios para el número en lugar del espacio necesario.

Sintaxis

$display("texto_con_formato", par_1, par_2, ... );
$strobe("texto_con_formato", par_1, par_2, ... );
$monitor("texto_con_formato", par_1, par_2, ... );
$displayb // similar al anterior pero por defecto en binario..
$strobeh // similar al anterior pero por defecto en hex..
$monitoro (// similar al anterior pero por defecto en octal..

Ejemplo

initial begin // c below is in submodule submod1
  $displayh (b, d, submod1.c); //No format, display in hex
  $monitor ("time=%t, d=%h, c=%b", $time, a, submod1.c);
end

$time, $stime, $realtime

Estos devuelven el tiempo de simulación actual como un entero de 64 bits, un entero de 32 bits y un número real, respectivamente.

Sintaxis

variable_integer=$time;   // tiempo de simulación

$reset, $stop, $finish

$reset restablece la simulación al tiempo 0; $stop detiene el simulador y lo pone en el modo interactivo donde el usuario puede introducir comandos; $finish sale del simulador de nuevo al sistema operativo.

Sintaxis

$finish;
$stop;

$deposit

Pone un valor particular en un elemento net, sobrescribiendo lo que se puso allí desde el “circuito”. Bueno para bancos de pruebas.

Sintaxis

$deposit(nombre_net, valor);

Ejemplo

$deposit (b, 1'b0);
$deposit (outp, 4'b001x);// outp es un bus 4-bit

$random

Genera un entero aleatorio cada vez que se invoca. Si se necesita puede forzase la secuencia a partir de una semilla dada o tomar por defecto el reloj del sistema.

Sintaxis

xzz = $random[(integer)];

Ejemplo

reg [3:0] xyz;
initial begin
  xyz= $random (7); // Seed the generator so number
  // sequence will repeat if simulation is restarted.
  forever xyz = #20 $random;
  // The 4 lsb bits of the random integers will transfer into the
  // xyz. Thus xyz will be a random integer 0 ≤ xyz ≤ 15.

$dumpfile, $dumpvar, $dumpon, $dumpoff, $dumpall

Volcado de variables para la visualización en herramientas de simulación gráficas.

Sintaxis estándar (VCD)

$dumpfile("nombre_de_archivo");
$dumpvars([profundidad, módulo]);
$dumpon;
$dumpoff;

$shm_probe, $shm_open

Sintaxis de Cadence

$shm_open("nombre_de_archivo");
$shm_probe(módulo, "profundidad");
$shm_close("nombre_de_archivo");

$fopen, $fdisplay, $fstrobe $fmonitor and $fwrite

Escritura genérica en archivos.

Sintaxis

identificador $fopen("nombre_de_archivo");
$fclose(identificador);
$fdisplay(identificador, ("texto_con_formato", señal, señal,);

Ejemplo

module testbench:
  reg [15:0]a; reg clk; integer hand1;
  initial begin
    hand1=$fopen(“datastuff.txt”);
    forever @(posedge clk) begin
      $fstrobe (hand1,time=%5t, a=%h, c=%b”, $time, a, submod1.c);.
    end // Never put statements after a forever block.
  end
  initial begin
    clk=0; a=8’h2b;
    forever #5 clk=~clk;
  end // Never put statements after a forever block
  initial begin
    a=a+8;
    #3000 $fclose (hand1); // Close the file
    $finish;
  end
  submod submod1(a, clk); // with internal variable c.
endmodule
-------------------------------- Output ---------------------------
time= 5, a=2b, c=0
time= 10, a=2c, c=1

Modelado a nivel de puertas

Las puertas lógicas “primitivas” son parte del lenguaje de Verilog. Se pueden especificar dos propiedades:

Puertas básicas

Implementan las puertas lógicas básicas. Tienen una salida y una o más entradas. En la sintaxis de instanciación de la puerta que se muestra a continuación, GATE representa una de las palabras clave and, nand, or, nor, xor, xnor.

Sintaxis

GATE (drive_strength) #(delays) 
instance_name1(output, input_1, 
                       input_2, ..., input_N), 
instance_name2(outp, in1, in2, ..., inN); 

donde delays se corresponde con una de las siguientes especificaciones:

Ejemplo

and c1(o,a, b,c, d);                   // puerta AND de 4-entradas llamada c1 y 
    c2(p, f, g);                       // puerta AND de 2-entradas llamada c2. 
or #(4, 3) ig (o, a, b);               /* puerta OR llamada ig (nombre de instancia); 
                                          tiempo de subida = 4, tiempo de bajada = 3 */ 
xor #(5) xor1 (a, b, c);               // a = b XOR c después de 5 unidades de tiempo 
xor (pull1, strong0) #5 (a, b, c);     /* puerta idéntica a la anterior con 
                                          pull-up de intensidad pull1 y
                                          pull-down de intensidad strong0. */

Puertas buf, not

Estos implementan buffers e inversores, respectivamente. Tienen una entrada y una o más salidas. En la sintaxis de la instancia de puerta que se muestra a continuación, GATE representa la palabra clave buf o not

Sintaxis

GATE (drive_strength) #(delays) 
instance_name1(output_1, output_2, 
                    ..., output_n, input), 
instance_name2(out1, out2, ..., outN, in);

Ejemplo

not #(5) not_1 (a, c);       // a = NOT c después de 5 unidades de tiempo 
buf c1 (o, p, q, r, in);     // buffers de 4-salidas y 2-salidas respectivamente
    c2 (p, f, g);

Puertas triestado: bufif1, bufif0, notif1, notif0

Implementan buffers e inversores de 3 estados, propagan Z (triestado o alta impedancia) si su señal de control no está asertada. Pueden tener tres especificaciones de retardo: un tiempo de subida, un tiempo de caída, y un tiempo para entrar en triestado.

Ejemplo

bufif0 #(5) not_1 (BUS, A, CTRL);      /* BUS = A 
                                          5 unidades de tiempo después de bajar CTRL */ 
notif1 #(3,4,6) c1 (bus, a, b, cntr);  /* el bus pasa a triestado
                                          6 unidades de tiempo después de bajar CTRL */

Primitivas definidas por usuario (UDPs)

Técnica de modelado con la que el usuario puede diseñar nuevos elementos primitivos a modo de módulos:

Reglas

Ejemplo

ex_prim.v
primitive   carry(carryOut, carryIn, aIn, bIn);
     output  carryOut;
     input    carryIn,  aIn, bIn;
          table
               0    00  :  0;
               0    01  :  0;
               0    10  :  0;
               0    11  :  1;
               1    00  :  0;
               1    01  :  1;
               1    10  :  1;
               1    11  :  1;
           endtable
endprimitive

Organización de diseños

Recomendaciones

Eficiencia en simulación

Eficiencia de síntesis


Otros

Qualis98- Verilog HDL QUICK REFERENCE CARD

IEEE Std 1364™-2005