pub:vlog

Verilog HDL

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:

  • Nivel comportamental: descripción algorítmica o arquitectural (similar al código C con instrucciones if, case y loop).
  • Nivel de transferencia de registros (RTL, del inglés Register Transfer Level): utilizando registros conectados por ecuaciones booleanas.
  • Nivel de puertas: interconexión de puertas lógicas AND, NOR, etc.
  • Nivel de conmutación: interconexión de los transistores MOS que forman las puertas.

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:

  • Estructural, como la descripción del esquemático de un circuito sin almacenamiento, p.ej.:
assign a = b & c | d;
assign d = e & (~c);
  • Procedural, usado para circuitos con almacenamiento, o como una descripción dependiente de condiciones o estados, p.ej.:
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.

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++):

  • Comenzando el comentario con dos barras diagonales //. Todo el texto entre estos caracteres y el final de la línea será ignorado por el compilador Verilog.
  • Comentarios entre los caracteres /* y */. El uso de este método permite continuar los comentarios en más de una línea. Esto es bueno para “comentar” código de muchas líneas, o para comentarios breves intercalados en la línea.
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

  • 3'b001, un número de 3 bits
  • 5'd30, (= 5'b11110)
  • 16'h5ED4, (= 16'd24276)

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.

  • Símbolos permitidos: ABCDE…abcdef…1234567890 _$
  • Símbolos no permitidos: - & # @

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.

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

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

  • 0 (cero lógico, o condición falsa)
  • 1 (uno lógico, o condición verdadera)
  • x (valor lógico desconocido)
  • z (estado de alta impedancia)

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

  • 334 Número decimal de 32 bits
  • 32'b101 Número binario en 32 bits (ceros a la izquierda)
  • 3'b11 Número binario de 3 bits (es decir, 011)
  • 20'hf_ffff Número hexadecimal de 20 bits
  • 10'bZ Número de 10 bits, todos ellos en tercer estado
  • -4'b11 Número binario de 4 bits, complemento a 2 de 0011 = 1101
  • 'h8FF Número hexadecimal de 32 bits

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:

  • cada bit
  • partes del vector
  • vector completo

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

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:

  • wand (cable AND): el valor deriva de una puerta AND y de las entradas (drivers) de la misma (modela open-collector)
  • wor (cable OR): el valor depende de lógica OR y drivers conectadas a la misma (modela ECL)
  • tri (triestado): todos los drivers conectados a un tri deben ser z, excepto uno (que determina el valor del tri)
  • tri0 (pull-down), tri1 (pull-up): cuando no están conectados se asocian a la correspondiente resistencia.

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)

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

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
); 

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.

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;

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

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

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

  • Una variable tipo reg se trata como un número no negativo (sin signo) en operaciones aritméticas (+, *).
  • Si cualquiera de los bits del operando aritmético es X o Z, entonces el resultado es X.

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

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

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];

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

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

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

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

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;

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

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}}

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:

  • Un registro se considera como número no negativo en las operaciones aritméticas.
  • Si cualquier bit de un operando en una operación aritmética es X o Z, el resultado es X.
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)

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

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:

  • (i) por su nombre (conexión explícita), utilizando un punto (.), p.ej. “.template_port_name (name_of_wire_connected_to_port)
  • (ii) por posición (conexión implícita), colocando los puertos en exactamente las mismas posiciones en las listas de puertos tanto de la plantilla como de la instancia.

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.

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;

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.

  • Asignación continua
    • asigna valores a nets (wire)
    • proporciona una forma de modelar lógica combinacional sin especificar una interconexión de puertas
  • Asignación procedural
    • asigna valores a registros (reg)
    • dentro de bloques/procedimientos como always, initial, task, y function
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
  • Modela el comportamiento de la lógica combinacional
  • Son las asignaciones que se realizan a objetos de tipo net (wire):
    assign sal = a & b;
  • Evalúa la expresión de la parte derecha (RHS) cada vez que cambia un operando.
  • En este tipo de sentencias pueden definirse varias asignaciones a la vez:
    assign c = a & b,
           d = a | b,
           e = a ^ b;
  • Cuando se conecta un wire a la salida de un módulo, éste también realiza una asignación de este tipo.
  • Las sentencias de bloque begin … end se usan para agrupar varias sentencias en el lugar donde una sentencia se permite sintácticamente.
  • Tales lugares son funciones, bloques always e initial, sentencias condicionales if, case, y sentencias de bucle como for.
  • Estos bloques pueden ser nombrados de forma opcional, para ser desactivados o reactivados posteriormente con sentencias de tipo enable o disable.
  • También pueden incluir declaraciones reg, integer y parameter.
  • Sintáxis:
begin : nombre_bloque
reg [msb:lsb] lista_variables_reg;
integer [msb:lsb] lista_enteros;
parameter [msb:lsb] lista_parametros;
... sentencias ...
end
  • Sólo se recomienda su uso a efectos de simulación (no síntesis).
  • Ejemplo:
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

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.

  • Sintáxis:
if (expresion)
begin
... sentencias ...
end
else if (expresion)
begin
... sentencias ...
end
... más bloques else if ...
else
begin
... sentencias ...
end
  • Ejemplos:
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

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.

  • Sintáxis:
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.

  • Ejemplos:
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.

  • Ejemplos:
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
  • Los bloques always sirven para definir el comportamiento del hardware, codificando algoritmos de procesamiento mediante sentencias de ejecución secuencial como las que existen en los lenguajes de programación.
  • Los bloques always se ejecutan cuando ocurren los eventos a los que son sensibles.
    always @(<eventos>)
      begin
        // algoritmo de procesamiento
      end
  • Los bloques always se ejecutan concurrentemente entre sí y con el resto de las asignaciones continuas que existan.
  • Los eventos que pueden disparar la ejecución de un bloque always son cambios de valor de cualquiera de las señales a las que son sensibles:
    always @(a or b or c or d) // sensible a: a, b, c, d
  • o flancos de alguna señal (típicamente un reloj):
    always @(posedge clk) // sensible a flanco + de clk
 
    always @(negedge clk) // sensible a flanco -de clk
  • Las señales a las que es sensible un bloque always pueden ser wire o reg , pero dentro de estos bloques sólo se puede asignar valor a nodos tipo reg .
  • No es necesario declarar las dimensiones del vector en la lista de sensibilidad.
  • Ejemplos:
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
  • Dentro de un bloque always hay 2 tipos de sentencias de asignación de valor a registros:
    • asignaciones bloqueantes (blocking), que se indican con el signo “=“.
    • asignaciones no bloqueantes (non blocking), que se indican con ““.
  • Con “=“ los reg ’s se actualizan inmediatamente, mientras que con “⇐“ no se actualizan hasta que no finaliza la ejecución del bloque. Esto puede causar problemas sino se tiene en cuenta.
    • Por ejemplo, si b vale 0:
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
  • Con “=“ suelen modelarse algoritmos.
  • Con ““ suelen modelarse retardos y circuitos síncronos.
  • Dentro de los bloques always se suelen utilizar, sobre todo, sentencias if , case y sentencias de asignación; algunas veces bucles for y, rara vez, otras sentencias.
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
  • Obsérvese que sí sólo hay una sentencia en el bloque, se puede prescindir de las llaves begin y end .
  • Ejemplo de RAM asíncrona:
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
  • Un circuito digital puede modelarse mediante varios bloques always que se comunican mediante señales registradas (reg ’s) declarados en el módulo localmente.

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.

  • Sintáxis:
  for (count = valor1;
       count </<=/>/>= valor2;
       count = count +/- step)
    begin
    ... sentencias ...
    end
  • Ejemplo:
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.

  • Sintáxis:
while (expresion)
begin
... sentencias ...
end
  • Ejemplo:
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.

  • Sintáxis:
forever
begin
... sentencias ...
end
  • Ejemplo:
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.

  • Sintáxis:
repeat (numero_veces)
begin
... sentencias ...
end
  • Ejemplos:
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;
  • Ejemplo:
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

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

Hay 2 formas de describir circuitos combinacionales:

  • Mediante asignaciones continuas (assign);
  • Mediante bloques always.

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:

  • El objeto que modela la salida ha de ser un reg.
  • En la lista de sensibilidad deben aparecer TODAS las señales de entrada al circuito.
  • En la descripción debe asignarse valor a TODAS las salidas para todas las posibles combinaciones de entrada.
  • La inmensa mayoría de los circuitos combinacionales pueden describirse combinando sentencias if y case, y utilizando las operaciones aritméticas básicas y los operadores lógicos. Conviene utilizar siempre “patrones” probados y sencillos, es decir, no se trata de describir algoritmos de procesamiento utilizando bucles, funciones, etc, sino de repetir fórmulas probadas, aunque puedan resultar, a veces, menos compactas. De esta manera, se evitarán problemas.

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.

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 (<=).

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:

  • mediante un bloque always síncrono,
  • definiendo un always síncrono y otro combinacional,
  • o con 2 bloques combinacionales y uno secuencial.

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:

  • Se pueden utilizar nombres simbólicos para los estados, en vez de una señal de estado reg
  • Si se utilizan nombres simbólicos, se podrá dirigir de algún modo en el sintetizador la codificación de los estados.
  • Si no se utilizan nombres simbólicos, pueden utilizarse trucos, como definir transiciones de estado mediante el incremento de la variable de estado, que mejoran el resultado de la síntesis.
  • Cuando a lo largo de los estados se repiten demasiadas operaciones, pueden usarse tasks, a modo de macros, para compactar el código y facilitar su comprensión e implementación.

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:

  • Que los nombres de los puertos que se vayan a conectar entre módulos distintos, se llamen igual y, las que no, tengan un nombre distinto.
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);
...

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

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:

  • módulos
  • primitivas definidas por el usuario
  • puertas primitivas
  • asignaciones continuas
  • bloques initial y always

Existen 3 métodos para crear declaraciones de generar:

  • generación en bucle
  • generación condicional
  • generación por casos

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
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

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
  • `define
    • Definición de macros con o sin valor
    • Ej: `define MAX 100000
  • `ifdef <`else> `endif
    • Condicional de definición de macros
    • Ej: `ifdef SYNTH <verilog code> `endif
    • si SYNTH es una macro definida, entonces se “inserta” <verilog code>
  • `include
    • Insertar archivo
    • `include <verilog file>
  • `timescale
    • Especifica unidades de tiempo y precisión
    • Ej: `timescale 1ns / 10ps

`timescale unidad_de_tiempo_de_referencia / precisión

  • unidad_de_tiempo_de_referencia es la unidad en la que se miden los tiempos y los retardos.
  • precisión es la precisión con la que se redondean los retardos durante la simulación.
  • Sólo una sentencia `timescale por diseño.
  • Se incluye en el fichero que define el test.
  • Sólo son válidos los parámetros 1, 10 y 100.
  • Las unidades de medida válidas son:
    • s = segundos
    • ms = milisegundos
    • us = microsegundos
    • ns = nanosegundos
    • ps = picosegundos
    • fs = femtosegundos

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

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

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

  • drive_strength (intensidad en las salidas de la puerta). La salida más fuerte es una conexión directa a una fuente, después una conexión a través de un transistor conductor, y después un pull-up/down resistivo. Normalmente no se especifica esta propiedad, en cuyo caso las intensidades predeterminadas son strong1 y strong0.
  • delay (retardos de propagación). Si no se especifica ningún retardo, entonces la puerta no tiene retardo de propagación; si se especifican dos retardos, el primero representa el retardo de la subida, el segundo el retardo de la caída; si sólo se especifica un retraso, entonces el de subida y caida son iguales. Los retrasos se modelan en simulación, pero se ignoran en síntesis. Este método de especificación de retardo es un caso especial de parámetros predefinidos en módulos parametrizados para el caso de las puertas primitivas.

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:

  • #(rise, fall)
  • #rise_and_fall
  • #(rise_and_fall)

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

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);

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

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

  • UDP combinacional
  • UDP secuencial (flip-flops, latches, …)

Reglas

  • Cada UDP tiene sólo una salida (no inout)
  • La salida tiene que ser el primer puerto de la lista
  • Todos los puertos son escalares
  • Opera con 3 estados: 0, 1, X (no Z)
  • Pueden asociarse retardos
  • Los entradas en Z son tratadas como X
  • No pueden hacer instanciaciones, ni asignaciones continuas ni procedurales
  • No puede haber la misma combinación de entradas para la misma salida

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

Recomendaciones

  • Cada módulo debe residir en un archivo, evitando que un archivo contenga más de un módulo
  • Nombre del archivo = nombre del módulo + .v, evitando que el nombre sea largo (>8 caracteres) para evitar problemas de portabilidad entre distinto sistemas de archivos
  • Para mejorar la diferenciación pueden utilizarse minúsculas para archivos/módulos RTL y mayúsculas para los estructurales
  • No mezclar en un mismo archivo descripciones RTL con instanciaciones estructurales
  • Los archivos pertenecientes a cada bloque funcional se almacenarán en un directorio con el nombre del módulo/bloque top
  • Las simulaciones se realizarán a partir de archivos de proyecto que incluyen la lista de los archivos que implica, normalmente archivo .prj
  • Los bancos de prueba residirán en archivos cuyo nombre corresponde al módulo correspondiente, p.ej. top.v más un sufijo que indique dicho propósito, p.ej. top_tb.v
  • Cada módulo incluirá cabecera de autor y versión, así como comentarios sobre su funcionalidad e interfaz

Eficiencia en simulación

  • Modelo_A es más eficiente en simulación que el Modelo_B si ambos son funcionalmente equivalentes pero el tiempo de simulación de Modelo_A es menor

Eficiencia de síntesis

  • Modelo_A es más eficiente en síntesis que el Modelo_B si ambos describen el mismo hardware, pero una o más de las siguientes posibilidades es cierta y prioritaria sobre otras condiciones,
    1. La temporización del camino crítico del Modelo_A es menor que la del Modelo_B
    2. El área del Modelo_A es menor que la del Modelo_B
    3. El Modelo_A requiere menos tiempo para sintetizar que el Modelo_B


  • pub/vlog.txt
  • Última modificación: 2021/11/19 12:49
  • por josan