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:
- Nivel comportamental: descripción algorítmica o arquitectural (similar al código C con instrucciones
if
,case
yloop
). - 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.
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++):
- 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 bits5'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.
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) input … output … inout … // conexiones internas, variables, ... wire … reg … integer … real … time … // 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:
- 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>
Tipo | Prefijo | Caracteres permitidos |
---|---|---|
binario | 'b | 01xXzZ_? |
octal | 'o | 0-7xXzZ_? |
decimal | 'd | 0-9_ |
hexadecimal | 'h | 0-9a-fA-FxXzZ_? |
Ejemplos
334
Número decimal de 32 bits32'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 bits10'bZ
Número de 10 bits, todos ellos en tercer estado-4'b11
Número binario de 4 bits, complemento a 2 de0011
=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
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:
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 untri
deben serz
, excepto uno (que determina el valor deltri
)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)
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
- 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
oZ
, entonces el resultado esX
.
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.
Operador | Descripció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.
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:
- (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.
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
- 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
, yfunction
Tipo de sentencia | Destino (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
- 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.
Bloques begin ... end
- 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
einitial
, sentencias condicionalesif
,case
, y sentencias de bucle comofor
. - Estos bloques pueden ser nombrados de forma opcional, para ser desactivados o reactivados posteriormente con sentencias de tipo
enable
odisable
. - También pueden incluir declaraciones
reg
,integer
yparameter
. - 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
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.
- 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
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.
- 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
Bloques always
- 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.
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
.
- 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
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-endfunction
y 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:
- 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
ycase
, 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.
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:
- 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.
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:
- 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); ...
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:
- módulos
- primitivas definidas por el usuario
- puertas primitivas
- asignaciones continuas
- bloques
initial
yalways
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
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
- `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
= segundosms
= milisegundosus
= microsegundosns
= nanosegundosps
= picosegundosfs
= 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
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:
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 sonstrong1
ystrong0
.
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.
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:
- #(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. */
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:
- 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
Organización de diseños
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,
- La temporización del camino crítico del Modelo_A es menor que la del Modelo_B
- El área del Modelo_A es menor que la del Modelo_B
- El Modelo_A requiere menos tiempo para sintetizar que el Modelo_B
Otros
dokuwiki\Exception\FatalException: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
An unforeseen error has occured. This is most likely a bug somewhere. It might be a problem in the authplain plugin.
More info has been written to the DokuWiki error log.