Programación en Verilog/Módulos
Características
editarLas características principales de los módulos se pueden recoger en los siguientes puntos.
- Cada módulo dispone de una serie de entradas y salidas, por las cuales se puede interconectar otros módulos, aunque puede no tener entradas ni salidas, como es el caso de los testbenches.
- No existen variables globales.
- Fuera de los módulos solo hay directivas del compilador, que afectarán a partir del punto en donde aparecen.
- A pesar de que se pueden realizar varias simulaciones concurrentes, en general se suele tener un único módulo que emplea los módulos previamente definidos.
- Cada módulo puede describirse de forma arquitectural o de comportamiento.
Estructura
editarEn Verilog un sistema digital esta compuesto por la interconexión de un conjunto de módulos. La estructura general de estos módulos es la siguiente.
module <nombre> (<señales>); <declaración de señales> <funcionalidad del módulo> endmodule
A continuación, se muestra un ejemplo de un módulo que suma un array de 3 bits indicando si hay acarreo o no.
module sum(A, B, R, C);
input [2:0] A, B;
output [2:0] R;
output C;
reg [2:0] R;
reg C;
always @(A or B)
begin
{C,R} = A + B;
end
endmodule
Interfaz del módulo
editarLos argumentos del módulo pueden ser de tres tipos, estos argumentos comunicarán el interior o funcionalidad del módulo con otros elementos del propio diseño.
- input: Entradas del módulo, cuyo tipo son wire.
- output: Salidas del módulo. Dependiendo del tipo de asignación que las genere serán wire si proceden de una asignación continua y reg si proceden de una asignación procedural.
- inout: Son a la vez entradas y salidas. Únicamente, son de tipo wire.
Procesos
editarUno de los aspectos que diferencian Verilog de los lenguajes procedurales como es el lenguaje c, es la posibilidad de ejecutar en paralelo varios procesos, aspecto que es fundamental en el lenguaje. Cada proceso se ejecuta de forma secuencial, es decir se ejecuta cada línea del proceso según su orden de precedencia.
Toda la descripción del comportamiento en Verilog debe declararse dentro de un proceso, estos pueden ser de dos tipos: initial o always.
- Initial: Este tipo de procesos se ejecuta sólo una vez comenzando su ejecución al inicio, y por tanto no existen retardos. Este proceso no es sintetizable, es decir, no puede ser utilizado en una descripción RTL. Su uso esta justificado en los testbenches, para la verificación de los distintos diseños.
initial begin ..... // Sentencias end
A continuación se muestra un ejemplo de este tipo de procesos.
initial
begin
clk = 0;
rst = 0;
enable = 0;
data = 0;
end
- Always: Este tipo de procesos se ejecuta continuamente a modo de bucle, y como su propio nombre indica, está continuamente ejecutándose. Este proceso si que es sintetizable y es controlada por la temporización o por eventos. Si dicho bloque se ejecuta por más de un evento, dicho conjunto se denomina lista sensible. La sintaxis de este proceso es la siguiente.
always [<temporizacion> | <@(lista sensible>] begin ..... // Sentencias end
El siguiente código muestra un ejemplo de los procesos always.
always @(a or b or sel)
begin
if (sel == 1)
y = a;
else
y = b;
end
Si el proceso engloba más de una asignación procedural o más de una estructura de control, estas deben estar contenidas en un bloque delimitado entre las palabras reservadas begin y end. Es decir, en el ejemplo no es necesario incluir estas palabras.
Otro ejemplo de los procesos always es el siguiente, donde no existe una lista de sensibilidad.
always
begin
# (10) clk = 0;
# (10) clk = 1;
end
En este proceso pasadas 10 unidades de tiempo se asigna a 0 la variable clk, después de otras 10 unidades se asigna a 1, comenzando de nuevo esta ejecución.
Estructuras de control
editarVerilog dispone de una elevada cantidad de estructuras de control, similares a las disponibles en otros lenguajes de programación. A continuación, se enumeran las estructuras que pueden ser utilizadas.
if - else
editarLa sentencia condicional if-else controla la ejecución de otras sentencias y/o asignaciones. En caso de haber múltiples sentencias o asignaciones es necesario hacer uso del bloque begin-end. La sintaxis de esta estructura es la siguiente.
if (expresion) begin Sentencias 1; end else begin Sentencias 2; end
Si la expresión es cierta se ejecutan las Sentencias 1, en caso contrario se ejecutan las Sentencias 2. Un ejemplo puede ser el siguiente.
// sel: 1'b1
if (sel == 1)
A = 4'b1010;
else
A = 4'b0101;
// Resultado A: 4'b1010
Case
editarLa sentencia case evalúa una expresión y en función de su valor ejecutará la sentencia o grupo de sentencias agrupadas en el primer caso que coincida. Si existen varias sentencias se deben agrupar en un bloque begin-end. Si no se cubren todos los posibles valores se debe hacer uso del caso por defecto, denominado default, el cual se ejecutará cuando ninguno de los casos anteriores se cumplan. La sintaxis de esta estructura es la siguiente.
case (expresion) caso 1: begin Sentencias 1; end caso 2: begin Sentencias 2; end ..... default: begin Sentencias D; end endcase
A continuación se detalla un ejemplo, donde dependiendo del valor que tome la expresión se asignará a la variable A un valor.
// sel: 2'b01
case (sel)
2'b00: A = 4'b0011;
2'b01: A = 4'b1100;
default: A = 4'b1111;
endcase
// Resultado A: 4'b1100
Casez y Casex
editarEstas estructuras corresponden a versiones especiales del case, cuya particularidad radica en que los valores lógicos 'X' y 'Z' se tratan como indiferentes.
- casez: Usa el valor 'Z' como indiferente.
- casex: Toma como indiferentes los valores lógicos 'Z' y 'X'.
Un ejemplo del tipo de la estructura casez se muestra en las siguientes líneas.
// sel: 3'b101
casez (sel)
3'b1zz: A = 4'b0011; // Se ejecuta siempre que el primer bit sea 1
3'b01?: A = 4'b1100; // ? significa indiferente
default: A = 4'b1111;
endcase
// Resultado A: 4'b11
Otro ejemplo para el tipo casex sería el siguiente.
// sel: 3'bz01
casex (sel)
3'b10X: A = 4'b0011; // X significa cualquier valor
3'bX0?: A = 4'b1100; // ? significa indiferente
default: A = 4'b1111;
endcase
// Resultado A: 4'b1100
For
editarEl bucle for es idéntico a los utilizados en otros lenguajes de programación, como en los anteriores pasos si existen varias sentencias se deben incluir dentro del bloque begin-end. Su sintaxis es la siguiente.
for (<valor inicial>, expresion, <incremento>) begin Sentencias; end
Un ejemplo de esta estructura es la siguiente.
for(i=0; i<64; i=i+1)
begin
A[i] = i;
B[i] = 0;
end
While
editarEl bucle while se ejecuta mientras la condición que evalúa sea cierta. Su sintaxis es la siguiente.
while (<expresion>) begin Sentencias; end
A continuación se muestra un ejemplo de este tipo de bucle.
while (data != 4'b0101)
begin
A = A + 1;
if (data[0] == 1'b0)
B = C;
else
B = D;
end
Repeat
editarEL bucle repeat se ejecuta un determinado número de veces, siendo este número la condición de salida del bucle. SU sintaxis es la que se muestra en las siguientes líneas.
repeat (<numero>) begin Sentencias; end
A continuación se muestra un ejemplo del bucle repeat, el cual se repetirá 9 veces.
repeat (9)
begin
tmp = data[0];
data = {data << 1, tmp};
end
Forever
editarEl bucle forever se ejecuta de forma continua sin condición de finalización. Su sintaxis es la siguiente.
forever begin Sentencias; end
Un ejemplo de este bucle podría ser el siguiente
forever
# 5 clk = !clk;
Wait
editarLa ejecución de un bloque se realiza de forma continuada, como si de un bucle infinito se tratara (se ejecutan todas las sentencias y se vuelven a repetir). Sería interesante poder parar la ejecución. Una forma de hacerlo e mediante las listas de sensibilidad y la otra forma de hacerlo es mediante la sentencia wait. Su sintaxis es la siguiente.
wait (expresion);
Un ejemplo de wait podría ser el siguiente
initial
begin
A = 1;
wait(B == 1);
A = 0;
end
Asignaciones
editarExisten dos maneras de asignar valores en Verilog, de forma continua o procedural.
Asignación continua
editarLa asignación continua se utiliza exclusivamente para modelar lógica combinacional, donde no se necesita de una lista de sensibilidad para realizar la asignación. La variable a la que se realiza la asignación continua sólo puede ser declarada de tipo net, es decir wire. Y la asignación sólo puede ser declarada fuera de cualquier proceso y nunca dentro de bloques always o initial. La sintaxis es la siguiente.
assign variable <#delay> = asignación
Un ejemplo de la asignación continua podría ser el siguiente.
assign A = B & C;
En el ejemplo no se ha añadido una temporización en la asignación, por lo tanto en cada ciclo se revisará dicha sentencia. Estas asignaciones también se ejecutan de forma secuencial las cuales se pueden controlar mediante la temporización.
Asignación procedural
editarLa asignación procedural es la que se ha visto hasta el momento, donde a las variables se le asigna un valor dentro de un proceso always o initial, el tipo de variable a la que se le asigna el valor puede ser de cualquier tipo. Su sintaxis es la siguiente.
<#delay> variable = asignación
Un ejemplo de esta asignación es el siguiente.
A = B & C;
Temporizaciones
editarLas temporizaciones se consiguen mediante el uso de retardos. El retardo se especifica mediante el símbolo # seguido de las unidades de tiempo del retardo. Un ejemplo podría se el siguiente, donde en el instante 0 se ejecutan las dos primeras sentencias, cinco unidades de tiempo más tarde se realiza la tercera asignación y cuatro más tarde se ejecutan las dos últimas.
initial
begin
clk = 0;
rst = 0;
#5 rst = 1;
#4 clk = 1;
rst = 0;
end
En las temporizaciones caben destacar dos tipos de asignaciones procedurales.
- Con bloqueo: La asignación se realiza antes de proceder con la siguiente.
- Sin bloqueo: El término se evalúa en el instante actual, pero no se asigna hasta finalizar dicho instante.
initial
begin
// Asignación con bloqueo
a = 5;
#1 a = a + 1;
b = a + 1;
// Asignación sin bloqueo
a = 5;
#1 a <= a + 1;
b = a + 1;
end
En el ejemplo, inicialmente se realiza una asignación con bloqueo, donde el resultado será a = 6 y b = 7, ya que a se ha calculado previamente. Mientras que en la asignación sin bloque a y b serán 6, ya que a no ha sido calculado.
Eventos
editarLa asignación procedural puede ser controlada por el cambio de una variable en un proceso always, es decir por eventos. Para ello se utiliza el carácter @ seguido del evento que permite la ejecución de la asignación procedural. Se distinguen dos tipos de eventos.
- De nivel: El cambio de valor de una o un conjunto de variables controla el acceso.
always @(C or D)
B = C + D;
- De flanco: La combinación de flanco de subida (de 0 a 1) o de bajada (de 1 a 0).
always @(posedge clk)
B <= C + 1;
Parámetros
editarLos parámetros son como en otros lenguajes las denominadas constantes, su definición sólo puede realizarse en el interior de un módulo, como puede ser el tamaño de un bus. La sintaxis de los parámetros es la siguiente.
parameter nombre = valor;
Un ejemplo podría ser el siguiente.
parameter N = 8;