====== 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'' 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. ===== 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'**b**001'', un número de 3 bits * ''5'**d**30'', (= ''5'**b**11110'') * ''16'**h**5ED4'', (= ''16'**d**24276'') === 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//** ' ^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 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//** [:] ; 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//** [index] [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 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) ==== 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__: 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: 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: 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 {{:pub:and2.png?250}} ==== 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 {{:pub:chk_zero.png?250}} ==== 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; {{:pub:cond.png?150|}} ==== 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//** 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//** // 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//** // 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: module counter ( input clk, rst, output reg [7:0] count ); parameter tpd_clk_to_count = 1; parameter tpd_reset_to_count = 1; //... endmodule // counter 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'', y ''function'' ^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'' 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: 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_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 (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 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 (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 ({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: 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 (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 @() 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: // flip-flop always@(posedge clk) begin q = d; end // multiplexor always @(sel or ent1 or ent2) begin if(!sel) sal = ent1; else sal = ent2; end // flip-flop con reset asíncrono always @(posedge clk or negedge rstn) begin if(!rstn) q = 1’b0; else q = d; end // 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: always @(a) // a pasa a valer 1 begin b = a; // b vale 1 c = b; // c vale 1 end 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. always @(posedge clk) begin if(ce) begin rega = ent1; regb = ent2; end 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 * 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: 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]; 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: 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 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: 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 // 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 // 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'' 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. {{ :pub:ex_decod.png?200}} 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. 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 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. {{:pub:ex_sum4.png?400}} ==== 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** {{ :pub:ex_dffe.png?350}} 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** {{ :pub:ex_dffr.png?350}} 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** 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// (''**%%<=%%**''). {{:pub:blocking.png?400}} ==== 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** {{ :pub:fsm1.png?400}} 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** 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** 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//** 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//** 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< **//Ejemplo de memoria SRAM-2P//** 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'' 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 ===== 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//** 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 `endif'' * si SYNTH es una macro definida, entonces se “inserta” '''' * **`include** * Insertar archivo * ''`include '' * **`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'' ** `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 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. ==== 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. {{:pub:triestados.png?400|}} //**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**// 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 ===== {{ :pub:qualis98qrc-veriloghdl_ref.pdf |Qualis98- Verilog HDL QUICK REFERENCE CARD}} {{ :pub:ieee2005std1364-verilog.pdf | IEEE Std 1364™-2005}}