Funciones en Verilog: Guía completa con ejemplos prácticos para principiantes

目次

1. ¿Qué es una función en Verilog? (Conceptos básicos y su papel)

Verilog HDL (Hardware Description Language) es un lenguaje de descripción de hardware utilizado para diseñar y simular circuitos digitales. Dentro de este lenguaje, una function (función) es un mecanismo que permite modularizar operaciones específicas para facilitar su reutilización.

Comprender las funciones en Verilog no solo mejora la legibilidad y el mantenimiento del código, sino que también contribuye a un diseño de circuitos más eficiente. En este artículo explicaremos los conceptos básicos de las funciones en Verilog y cómo se utilizan en la práctica.

¿Qué es una function?

Una function en Verilog es un bloque que realiza un cálculo u operación específica y devuelve un único valor. Gracias a su uso, es posible reducir código redundante y simplificar el diseño de circuitos.

Características de una function

  • Acepta una o más entradas (solo se permite input)
  • Devuelve un único valor (valor de retorno)
  • No puede incluir retardos de tiempo (#10, por ejemplo)
  • Siempre describe lógica combinacional dentro de la función
  • Se define fuera de los bloques always y, a diferencia de las tasks, siempre se evalúa de manera inmediata

Casos de uso de las funciones en Verilog

Las functions en Verilog se utilizan principalmente en los siguientes escenarios:

1. Descripción de lógica combinacional

Dado que una función devuelve un resultado de inmediato en función de sus entradas, se utiliza frecuentemente para lógica combinacional (Combinational Logic).
Ejemplos: operaciones de suma, resta, codificadores y decodificadores.

2. Mejorar la reutilización del código

Permiten eliminar código redundante y encapsular procesos reutilizados varias veces.
Ejemplo: transformar una expresión condicional compleja en una función para mejorar la legibilidad del módulo.

3. Reducir errores de diseño

Al concentrar cálculos o expresiones lógicas en un único lugar, se minimizan errores al realizar cambios.
Ejemplo: cálculos de CRC (Comprobación de Redundancia Cíclica) o verificaciones de paridad.

Diferencias entre function y task

En Verilog también existe la estructura task (tarea). Aunque se parecen, tienen diferencias clave:

Aspectofunctiontask
SalidaÚnicaMúltiples posibles
Entradas
Variables internas
Retardos (#10)No permitidosPermitidos
Uso dentro de alwaysPermitidoNo permitido
Forma de llamadanombre_función(argumentos)nombre_task(argumentos);

Cuándo usar function

  • Cuando se necesita un resultado inmediato
  • Para lógica sin retardos
  • Cuando basta con devolver un único valor

Cuándo usar task

  • Procesos que incluyan retardos de tiempo (#10, por ejemplo)
  • Procesos que requieran múltiples salidas
  • Procesos de depuración para simulación (como generación de logs)

Resumen

  • Una function en Verilog recibe entradas y devuelve un único valor.
  • Es adecuada para describir lógica combinacional y no admite retardos.
  • Reduce la redundancia del código y mejora la legibilidad.
  • Se diferencia de las tasks, por lo que conviene elegir la estructura adecuada según el caso.

2. Cómo escribir una función en Verilog (con ejemplo para principiantes)

En la sección anterior vimos los conceptos básicos de las funciones en Verilog. Ahora explicaremos en detalle cómo se escriben y su sintaxis.

Sintaxis básica de una función

Una función en Verilog se define con la siguiente estructura:

function [ancho_salida] nombre_funcion;
    input [ancho_entrada] entrada1, entrada2, ...;
    begin
        nombre_funcion = expresión;
    end
endfunction

Puntos clave

  • Se declara con la palabra clave function
  • Una variable con el mismo nombre de la función sirve como valor de retorno
  • Las entradas se declaran con input (no se permiten output ni inout)
  • Dentro de begin ... end se escribe la lógica o el cálculo
  • Debe definirse fuera de los bloques always

Ejemplo sencillo de función en Verilog

A continuación, un ejemplo de una función que realiza una suma de 8 bits:

module example;
    function [7:0] add_function;
        input [7:0] a, b;
        begin
            add_function = a + b;
        end
    endfunction

    reg [7:0] x, y, sum;

    initial begin
        x = 8'b00001100; // 12
        y = 8'b00000101; // 5
        sum = add_function(x, y);
        $display("Sum: %d", sum); // Sum: 17
    end
endmodule

Explicación

  • add_function recibe dos entradas de 8 bits (a y b) y devuelve su suma
  • Se invoca con sum = add_function(x, y); asignando el resultado a la variable sum
  • Dentro del bloque initial se muestra el resultado con $display

Declaración de entradas y salidas en funciones

Entradas

Las funciones en Verilog solo permiten input como parámetros.

function [7:0] my_function;
    input [7:0] in1, in2;
    begin
        my_function = in1 & in2; // Operación AND
    end
endfunction

Nota: No se puede declarar output. El valor de salida se devuelve mediante la variable con el mismo nombre de la función.

Uso de estructuras condicionales

Dentro de una función también se pueden usar estructuras condicionales como if o case.

function [3:0] max_function;
    input [3:0] a, b;
    begin
        if (a > b)
            max_function = a;
        else
            max_function = b;
    end
endfunction

Esta función devuelve el valor mayor entre a y b.

Resumen

  • Las funciones en Verilog se definen con function y devuelven un único valor
  • Las entradas deben declararse como input exclusivamente
  • El resultado se asigna a la variable con el mismo nombre de la función
  • Es posible usar condicionales (if, case) dentro de una función
Ejemplo de código Verilog function

3. Uso de funciones en Verilog (con ejemplos prácticos)

En la sección anterior vimos la sintaxis básica de las funciones en Verilog. Ahora aprenderemos cómo usarlas en un diseño real mediante ejemplos prácticos.

Cómo invocar una función

Las funciones en Verilog se invocan igual que una variable, utilizando la sintaxis nombre_función(argumentos).
En el siguiente ejemplo se define una función XOR de 8 bits y se usa dentro de un módulo:

module function_example;
    function [7:0] xor_function;
        input [7:0] a, b;
        begin
            xor_function = a ^ b;
        end
    endfunction

    reg [7:0] x, y, result;

    initial begin
        x = 8'b11001100;
        y = 8'b10101010;
        result = xor_function(x, y); // llamada a la función
        $display("XOR Result: %b", result); // Resultado: 01100110
    end
endmodule

Puntos clave

  • Se invoca con la forma variable = function(argumentos);
  • Pueden usarse dentro de bloques always e initial
  • Funcionan como lógica combinacional

Uso en lógica combinacional

Dado que las funciones se evalúan de inmediato, son útiles para implementar lógica combinacional.
Ejemplo: un decodificador 2 a 4 implementado con function:

module decoder_example;
    function [3:0] decoder;
        input [1:0] sel;
        begin
            case (sel)
                2'b00: decoder = 4'b0001;
                2'b01: decoder = 4'b0010;
                2'b10: decoder = 4'b0100;
                2'b11: decoder = 4'b1000;
                default: decoder = 4'b0000;
            endcase
        end
    endfunction

    reg [1:0] select;
    wire [3:0] decoded_output;

    assign decoded_output = decoder(select);

    initial begin
        select = 2'b01;
        #10;
        $display("Decoded Output: %b", decoded_output); // 0010
    end
endmodule

Explicación

  • La función decoder convierte una entrada de 2 bits en una salida de 4 bits
  • El case selecciona la salida en función del valor de entrada
  • Con assign se conecta el resultado de la función a la salida decoded_output

Comparación entre function y always

Las funciones y los bloques always se usan para describir lógica, pero tienen diferencias en propósito y limitaciones.

Aspectofunctionalways
Lugar de definiciónFuera de bloques alwaysDentro de bloques always
EntradasSolo inputreg o wire
SalidaUn solo valorPuede actualizar múltiples señales
Retardos (#10)No permitidosPermitidos
EstadoNo almacena estado (evaluación inmediata)Puede almacenar estado (flip-flops)
Uso principalLógica combinacionalLógica secuencial o dependiente de eventos

Claves para elegir

  • Las functions son ideales para operaciones combinacionales simples
  • Los bloques always se usan cuando se necesita almacenamiento o retardos
  • Si se requiere un retardo (#10), debe usarse always, no function

Resumen del uso de funciones

✅ Se llaman con nombre_función(argumentos)
Adecuadas para lógica combinacional
✅ Soportan estructuras como case e if
✅ Útiles en decodificadores, operaciones aritméticas y de control

4. Ejemplos de aplicación de funciones en Verilog (Decodificadores y ALU)

Hasta ahora hemos visto la sintaxis y el uso básico de las funciones en Verilog. En esta sección mostraremos cómo aplicarlas en diseños de circuitos digitales reales, tomando como ejemplo un decodificador y una ALU.

Implementación de un decodificador 2 a 4 con function

Un decodificador convierte un número reducido de bits de entrada en múltiples salidas.
Por ejemplo, un decodificador 2 a 4 transforma 2 bits de entrada en 4 salidas. Con una function se puede escribir de forma compacta:

module decoder_example;
    function [3:0] decoder;
        input [1:0] sel;
        begin
            case (sel)
                2'b00: decoder = 4'b0001;
                2'b01: decoder = 4'b0010;
                2'b10: decoder = 4'b0100;
                2'b11: decoder = 4'b1000;
                default: decoder = 4'b0000;
            endcase
        end
    endfunction

    reg [1:0] select;
    wire [3:0] decoded_output;

    assign decoded_output = decoder(select);

    initial begin
        select = 2'b00; #10;
        $display("Decoded Output: %b", decoded_output);
        select = 2'b01; #10;
        $display("Decoded Output: %b", decoded_output);
        select = 2'b10; #10;
        $display("Decoded Output: %b", decoded_output);
        select = 2'b11; #10;
        $display("Decoded Output: %b", decoded_output);
    end
endmodule

Implementación de una ALU con function (suma, resta, AND, OR)

Una ALU (Unidad Lógica Aritmética) es un bloque fundamental en una CPU que realiza operaciones aritméticas y lógicas. A continuación, un ejemplo de ALU simple de 8 bits implementada con function:

module alu_example;
    function [7:0] alu;
        input [7:0] a, b;
        input [1:0] op; // señal de control de 2 bits
        begin
            case (op)
                2'b00: alu = a + b; // Suma
                2'b01: alu = a - b; // Resta
                2'b10: alu = a & b; // AND
                2'b11: alu = a | b; // OR
                default: alu = 8'b00000000;
            endcase
        end
    endfunction

    reg [7:0] x, y;
    reg [1:0] opcode;
    wire [7:0] result;

    assign result = alu(x, y, opcode);

    initial begin
        x = 8'b00001100; // 12
        y = 8'b00000101; // 5

        opcode = 2'b00; #10;
        $display("Addition Result: %d", result); // 12 + 5 = 17

        opcode = 2'b01; #10;
        $display("Subtraction Result: %d", result); // 12 - 5 = 7

        opcode = 2'b10; #10;
        $display("AND Result: %b", result);

        opcode = 2'b11; #10;
        $display("OR Result: %b", result);
    end
endmodule

Resumen

✅ Las functions se pueden aplicar a diseños combinacionales como decodificadores y ALUs
✅ Con case es posible describir operaciones de forma flexible
✅ Mejoran la legibilidad y facilitan la reutilización del código
✅ Son óptimas para lógica combinacional, pero no para lógica secuencial (no admiten retardos)

5. Puntos a tener en cuenta al usar funciones en Verilog

Las funciones en Verilog son herramientas potentes para mejorar la legibilidad y reutilización del código, pero tienen ciertas limitaciones que deben considerarse. A continuación, se detallan los aspectos más importantes.

No se permite la recursividad

En Verilog no está permitido el uso de llamadas recursivas. Es decir, una función no puede llamarse a sí misma dentro de su definición.

❌ Ejemplo incorrecto (recursión)

function [3:0] factorial;
    input [3:0] n;
    begin
        if (n == 0)
            factorial = 1;
        else
            factorial = n * factorial(n - 1); // ❌ No permitido
    end
endfunction

Este código provocará un error en simulación.

✅ Solución: usar bucles

Si se necesita comportamiento recursivo, debe implementarse con un bucle dentro de un bloque always o bien mediante una task.

task factorial_task;
    input [3:0] n;
    output [15:0] result;
    integer i;
    begin
        result = 1;
        for (i = 1; i <= n; i = i + 1)
            result = result * i;
    end
endtask

No se permiten retardos dentro de una función

Las funciones en Verilog se evalúan de manera inmediata (lógica combinacional), por lo que no se pueden incluir retardos (#10, por ejemplo).

❌ Ejemplo incorrecto

function [7:0] delay_function;
    input [7:0] in;
    begin
        #10; // ❌ No permitido
        delay_function = in + 1;
    end
endfunction

✅ Solución: usar always o task

Si se necesita un retardo, debe implementarse en un bloque always o en una task.

task delay_task;
    input [7:0] in;
    output [7:0] out;
    begin
        #10;
        out = in + 1;
    end
endtask

Diferencias clave entre function y task

En la siguiente tabla se resumen sus diferencias:

Aspectofunctiontask
SalidaÚnica (valor de retorno)Múltiples posibles
EntradasSolo inputinput y output
Variables internasPermitidasPermitidas
Retardos (#10)No
Uso en alwaysPermitidoNo permitido
Llamadanombre_función(arg)nombre_task(arg);

Cuándo usar function

✅ Cuando se necesita un resultado inmediato (suma, resta, operaciones lógicas)
✅ Para lógica combinacional sin retardos
✅ Cuando solo se requiere un valor de retorno

Cuándo usar task

✅ Cuando se necesitan retardos (#10)
✅ Para procesos con múltiples salidas
✅ Para depuración en simulación (mensajes, monitoreo)

No se pueden definir funciones dentro de un bloque always

Las funciones deben declararse fuera de los bloques always. Si se intentan definir dentro, se producirá un error de compilación.

❌ Ejemplo incorrecto

always @(a or b) begin
    function [7:0] my_function; // ❌ No permitido
        input [7:0] x, y;
        begin
            my_function = x + y;
        end
    endfunction
end

✅ Ejemplo correcto

function [7:0] add_function;
    input [7:0] x, y;
    begin
        add_function = x + y;
    end
endfunction

always @(a or b) begin
    result = add_function(a, b);
end

Resumen

✅ Las funciones no admiten recursión (usar bucles o tasks)
No permiten retardos (usar always o tasks)
No pueden definirse dentro de bloques always
✅ Devuelven un solo valor (para múltiples salidas, usar tasks)
✅ Es fundamental elegir correctamente entre function y task según la necesidad

6. Preguntas frecuentes (FAQ) sobre funciones en Verilog

En las secciones anteriores revisamos los conceptos básicos, usos y limitaciones de las funciones en Verilog. A continuación, respondemos algunas de las preguntas más comunes sobre este tema.

¿Cuál es la diferencia entre function y task?

Q. ¿En qué se diferencian y cuándo debo usar cada una?

A. Una function devuelve un único valor de manera inmediata; una task puede tener múltiples salidas y admitir retardos.

Aspectofunctiontask
SalidaÚnicaMúltiples
EntradasSolo inputinput y output
Variables internasPermitidasPermitidas
Retardos (#10)No permitidosPermitidos
Uso en alwaysNo
Llamadanombre_función(arg)nombre_task(arg);

Cuándo usar function

✅ Para obtener un resultado inmediato (suma, resta, operaciones lógicas)
✅ Para lógica combinacional sin retardos
✅ Cuando solo se necesita un valor de salida

Cuándo usar task

✅ Cuando se necesitan retardos (#10)
✅ Cuando hay múltiples salidas
✅ Para depuración en simulaciones (logs, monitoreo)

¿Se puede usar reg dentro de una función?

Q. ¿Es posible declarar variables tipo reg dentro de una función?

A. No, dentro de una función no se permiten variables reg, pero sí integer.

Ejemplo correcto usando integer:

function [7:0] multiply;
    input [3:0] a, b;
    integer temp;
    begin
        temp = a * b;
        multiply = temp;
    end
endfunction

¿En qué situaciones se recomienda usar funciones?

Q. ¿Cuáles son los casos de uso más comunes?

A. Son ideales para operaciones simples y lógica combinacional.

Ejemplos típicos:

  • Operaciones aritméticas (suma, resta, AND, OR)
  • Decodificadores o codificadores
  • Comparaciones (máximo/mínimo)
  • Verificaciones (paridad, CRC)

No son adecuadas para lógica secuencial (flip-flops, contadores).

¿Se puede invocar una función desde otra función?

Q. ¿Es posible anidar llamadas de funciones?

A. Sí, es posible, pero hay que cuidar la dependencia entre funciones.

function [7:0] add;
    input [7:0] a, b;
    begin
        add = a + b;
    end
endfunction

function [7:0] double_add;
    input [7:0] x, y;
    begin
        double_add = add(x, y) * 2;
    end
endfunction

¿Cómo elegir entre function y always?

Q. ¿Cuál es la diferencia práctica entre una función y un bloque always?

A. Las functions son para lógica combinacional, los bloques always para lógica secuencial.

Aspectofunctionalways
Retardos (#10)No
EstadoNo almacena estado (evaluación inmediata)Puede almacenar estado (flip-flops, registros)
Uso principalLógica combinacionalLógica secuencial

Ejemplo con function

function [7:0] add;
    input [7:0] a, b;
    begin
        add = a + b;
    end
endfunction

Ejemplo con always

always @(posedge clk) begin
    sum <= a + b;
end

Resumen

Las funciones son ideales para lógica combinacional y operaciones simples
Las tasks permiten retardos y múltiples salidas
Los bloques always se usan para lógica secuencial
✅ Importante: elegir la estructura adecuada según el diseño