Programación en Verilog/Módulos

← Elementos básicos del lenguaje Módulos Jerarquía →




Características editar

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

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

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

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

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

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

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

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

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

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

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

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

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

Existen dos maneras de asignar valores en Verilog, de forma continua o procedural.

Asignación continua editar

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

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

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

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

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


← Elementos básicos del lenguaje Módulos Jerarquía →