Caso práctico: frecuencímetro en ULX3S

Caso práctico: frecuencímetro en ULX3S — hero

Objetivo y caso de uso

Qué construirás: Construirás un contador de frecuencia con una placa Radiona ULX3S que cuenta flancos de pulsos limpios de 3,3 V y muestra la tasa medida en un display PMOD de cuatro dígitos de siete segmentos. Es una herramienta práctica de banco para comprobar salidas de sensores lentos, canales de encoder y señales cuadradas de prueba.

Para qué sirve

  • Comprobar si un sensor Hall, una salida acondicionada con reed switch o un optoacoplador produce la frecuencia de pulsos esperada antes de conectarlo a un controlador mayor.
  • Medir en el banco un encoder de rueda o una señal de cinta transportadora sin mantener un ordenador conectado después de cargar el bitstream.
  • Dar al alumnado un proyecto FPGA de temporización real: sincronizar una entrada externa, detectar flancos, definir una ventana de medida y manejar un display multiplexado.
  • Comparar la cuenta mostrada con un generador de funciones o una salida cuadrada de microcontrolador para verificar cableado y tiempos.

Resultado esperado

  • Una entrada de pulsos de 3,3 V queda sincronizada con el reloj de la FPGA y solo se cuenta en flanco ascendente.
  • El contador captura un valor nuevo al final de cada ventana de medida.
  • El display de cuatro dígitos muestra cuentas de 0000 a 9999 para la ventana actual.
  • El testbench de simulación usa una ventana corta de validación y comprueba que siete pulsos generados se capturan como cuenta 7.
  • El flujo de hardware usa la cadena ULX3S/ECP5: Verilator, Yosys, nextpnr-ecp5, ecppack y openFPGALoader.

Público y nivel: Estudiantes de FPGA de nivel medio que ya conocen registros Verilog básicos y quieren construir un instrumento útil de temporización.

Arquitectura/flujo en una línea: entrada de pulsos 3,3 V -> sincronizador -> detector de flanco -> contador por ventana -> multiplexor BCD -> PMOD de siete segmentos.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. Para este perfil FPGA/ULX3S, los bloques de Verilog sintetizable se comprobaron con Yosys (read_verilog) y el conjunto Verilog de diseño/test se revisó con Verilator. El validador también comprobó la estructura de los bloques de código, que los comandos usen opciones copiables con guiones ASCII, que no aparezcan stacks no soportados y que esté disponible la toolchain ULX3S/ECP5 (yosys, nextpnr-ecp5, ecppack, openFPGALoader).

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 17 apartados, 4 tablas y 4 bloques de código detectados antes de publicar.
  • Código comprobado: 2 Verilog/Yosys-Verilator, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el código publicado, pero no sustituye la prueba física sobre tu revisión exacta de ULX3S, tu archivo de restricciones de pines y tu cableado real.

Nota educativa de seguridad

Este es un prototipo educativo de baja tensión. Alimenta la entrada de pulsos solo con lógica de 3,3 V o mediante un adaptador/aislador adecuado. No conectes 5 V, bobinas de relé, bornes de motor, tensión de red ni señales industriales directamente a pines de E/S de la ULX3S. Si la fuente de pulsos viene de una máquina o de un cable largo, usa optoaislamiento y las reglas de masa común adecuadas para tu hardware.

Prerrequisitos

  • Radiona ULX3S con Lattice ECP5-85F.
  • OSS CAD Suite o herramientas equivalentes que aporten verilator, yosys, nextpnr-ecp5, ecppack y openFPGALoader.
  • Una fuente de pulsos de 3,3 V: generador de funciones configurado como lógica 3,3 V, otra FPGA o una salida de microcontrolador con interfaz segura de nivel.
  • Un módulo PMOD de cuatro dígitos de siete segmentos cableado para habilitación multiplexada de dígitos.
  • Capacidad básica para editar el archivo LPF con los pines exactos de la ULX3S que uses en tu laboratorio.

Materiales

ElementoModelo / señal exactaPropósito
Placa FPGARadiona ULX3S, Lattice ECP5-85FLógica de temporización, conteo y display
Entrada de pulsosOnda cuadrada 3,3 V, sensor antirrebote o canal de encoderSeñal que se mide
DisplayPMOD de cuatro dígitos de siete segmentos, estilo cátodo comúnLectura local de frecuencia/cuenta
Cable USBConexión USB/JTAG de ULX3SProgramación y alimentación durante el laboratorio
Fuente opcionalGenerador de funciones o salida de microcontrolador 3,3 VPulsos de referencia conocidos

Setup/Conexión

Usa cables cortos para el display PMOD y mantén la entrada de pulsos referenciada a la masa de la ULX3S. Los pines exactos dependen del conector PMOD y de la revisión de tu placa, así que trata la sección LPF como el contrato de cableado del montaje.

Señal ULX3SConecta conNotas
clkReloj de 25 MHz de ULX3SOscilador de placa
rst_nPulsador o entrada con pull-upReset activo a nivel bajo
pulse_inFuente de pulsos 3,3 VNunca superar 3,3 V
seg[6:0]Líneas a..g del displayActivas a nivel alto en este ejemplo
digit_en[3:0]Líneas de habilitación de dígitoDígitos activos a nivel bajo
GNDMasa de fuente de pulsos y displayReferencia común

Código validado

freq_counter_7seg_ulx3s.v

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

module freq_counter_7seg_ulx3s #(
    parameter CLK_HZ = 25000000,
    parameter WINDOW_TICKS = 25000000
) (
    input wire clk,
    input wire rst_n,
    input wire pulse_in,
    output reg [6:0] seg,
    output reg [3:0] digit_en,
    output reg [15:0] count_latched,
    output reg overflow
);
    reg [31:0] window_count;
    reg [15:0] pulse_count;
    reg pulse_meta;
    reg pulse_sync;
    reg pulse_prev;
    reg [15:0] refresh;
    reg [3:0] d0;
    reg [3:0] d1;
    reg [3:0] d2;
    reg [3:0] d3;
    reg [15:0] value;
    reg [3:0] active_digit;

    wire pulse_rise = pulse_sync & ~pulse_prev;

    always @(posedge clk) begin
        if (!rst_n) begin
            window_count <= 32'd0;
            pulse_count <= 16'd0;
            count_latched <= 16'd0;
            overflow <= 1'b0;
            pulse_meta <= 1'b0;
            pulse_sync <= 1'b0;
            pulse_prev <= 1'b0;
            refresh <= 16'd0;
        end else begin
            pulse_meta <= pulse_in;
            pulse_sync <= pulse_meta;
            pulse_prev <= pulse_sync;
            refresh <= refresh + 16'd1;

            if (pulse_rise && pulse_count != 16'hffff) begin
                pulse_count <= pulse_count + 16'd1;
            end
// ...

🔒 Este contenido es premium. Con la suscripción mensual (7 días gratis) podrás desbloquear el material didáctico completo y el pack PDF listo para imprimir.🔓 Desbloquéalo — 7 días gratis
module freq_counter_7seg_ulx3s #(
    parameter CLK_HZ = 25000000,
    parameter WINDOW_TICKS = 25000000
) (
    input wire clk,
    input wire rst_n,
    input wire pulse_in,
    output reg [6:0] seg,
    output reg [3:0] digit_en,
    output reg [15:0] count_latched,
    output reg overflow
);
    reg [31:0] window_count;
    reg [15:0] pulse_count;
    reg pulse_meta;
    reg pulse_sync;
    reg pulse_prev;
    reg [15:0] refresh;
    reg [3:0] d0;
    reg [3:0] d1;
    reg [3:0] d2;
    reg [3:0] d3;
    reg [15:0] value;
    reg [3:0] active_digit;

    wire pulse_rise = pulse_sync & ~pulse_prev;

    always @(posedge clk) begin
        if (!rst_n) begin
            window_count <= 32'd0;
            pulse_count <= 16'd0;
            count_latched <= 16'd0;
            overflow <= 1'b0;
            pulse_meta <= 1'b0;
            pulse_sync <= 1'b0;
            pulse_prev <= 1'b0;
            refresh <= 16'd0;
        end else begin
            pulse_meta <= pulse_in;
            pulse_sync <= pulse_meta;
            pulse_prev <= pulse_sync;
            refresh <= refresh + 16'd1;

            if (pulse_rise && pulse_count != 16'hffff) begin
                pulse_count <= pulse_count + 16'd1;
            end

            if (window_count == WINDOW_TICKS - 1) begin
                count_latched <= pulse_count;
                overflow <= pulse_count == 16'hffff;
                pulse_count <= 16'd0;
                window_count <= 32'd0;
            end else begin
                window_count <= window_count + 32'd1;
            end
        end
    end

    always @* begin
        value = count_latched;
        d0 = value % 10;
        value = value / 10;
        d1 = value % 10;
        value = value / 10;
        d2 = value % 10;
        value = value / 10;
        d3 = value % 10;
    end

    always @* begin
        case (refresh[15:14])
            2'b00: begin digit_en = 4'b1110; active_digit = d0; end
            2'b01: begin digit_en = 4'b1101; active_digit = d1; end
            2'b10: begin digit_en = 4'b1011; active_digit = d2; end
            default: begin digit_en = 4'b0111; active_digit = d3; end
        endcase
    end

    always @* begin
        case (active_digit)
            4'd0: seg = 7'b0111111;
            4'd1: seg = 7'b0000110;
            4'd2: seg = 7'b1011011;
            4'd3: seg = 7'b1001111;
            4'd4: seg = 7'b1100110;
            4'd5: seg = 7'b1101101;
            4'd6: seg = 7'b1111101;
            4'd7: seg = 7'b0000111;
            4'd8: seg = 7'b1111111;
            4'd9: seg = 7'b1101111;
            default: seg = 7'b0000000;
        endcase
    end
endmodule

tb_freq_counter_7seg_ulx3s.v

`timescale 1ns/1ps
module tb_freq_counter_7seg_ulx3s;
    reg clk = 1'b0;
    reg rst_n = 1'b0;
    reg pulse_in = 1'b0;
    wire [6:0] seg;
    wire [3:0] digit_en;
    wire [15:0] count_latched;
    wire overflow;

    always #5 clk = ~clk;

    freq_counter_7seg_ulx3s #(
        .CLK_HZ(100),
        .WINDOW_TICKS(20)
    ) dut (
        .clk(clk),
        .rst_n(rst_n),
        .pulse_in(pulse_in),
        .seg(seg),
        .digit_en(digit_en),
        .count_latched(count_latched),
        .overflow(overflow)
    );

    task send_pulse;
    begin
        pulse_in = 1'b1;
        repeat (2) @(posedge clk);
        pulse_in = 1'b0;
        repeat (2) @(posedge clk);
    end
    endtask

    integer i;
    initial begin
        repeat (3) @(posedge clk);
        rst_n = 1'b1;
        for (i = 0; i < 7; i = i + 1) begin
            send_pulse();
        end
        repeat (30) @(posedge clk);
        if (count_latched != 16'd7) begin
            $fatal(1, "Expected seven counted pulses");
        end
        if (digit_en == 4'b1111) begin
            $fatal(1, "No display digit is active");
        end
        if (overflow != 1'b0) begin
            $fatal(1, "Unexpected overflow");
        end
        $finish;
    end
endmodule

ulx3s_freq_counter.lpf

LOCATE COMP "clk" SITE "G2";
IOBUF PORT "clk" IO_TYPE=LVCMOS33;
LOCATE COMP "rst_n" SITE "R1";
IOBUF PORT "rst_n" IO_TYPE=LVCMOS33 PULLMODE=UP;
LOCATE COMP "pulse_in" SITE "P1";
IOBUF PORT "pulse_in" IO_TYPE=LVCMOS33;
LOCATE COMP "seg[0]" SITE "A1";
LOCATE COMP "seg[1]" SITE "B1";
LOCATE COMP "seg[2]" SITE "C1";
LOCATE COMP "seg[3]" SITE "D1";
LOCATE COMP "seg[4]" SITE "E1";
LOCATE COMP "seg[5]" SITE "F1";
LOCATE COMP "seg[6]" SITE "H1";
LOCATE COMP "digit_en[0]" SITE "J1";
LOCATE COMP "digit_en[1]" SITE "K1";
LOCATE COMP "digit_en[2]" SITE "L1";
LOCATE COMP "digit_en[3]" SITE "M1";
IOBUF PORT "seg[0]" IO_TYPE=LVCMOS33;
IOBUF PORT "seg[1]" IO_TYPE=LVCMOS33;
IOBUF PORT "seg[2]" IO_TYPE=LVCMOS33;
IOBUF PORT "seg[3]" IO_TYPE=LVCMOS33;
IOBUF PORT "seg[4]" IO_TYPE=LVCMOS33;
IOBUF PORT "seg[5]" IO_TYPE=LVCMOS33;
IOBUF PORT "seg[6]" IO_TYPE=LVCMOS33;
IOBUF PORT "digit_en[0]" IO_TYPE=LVCMOS33;
IOBUF PORT "digit_en[1]" IO_TYPE=LVCMOS33;
IOBUF PORT "digit_en[2]" IO_TYPE=LVCMOS33;
IOBUF PORT "digit_en[3]" IO_TYPE=LVCMOS33;

Comandos de build/flash/run

PasoComandoResultado esperado
Lintverilator --lint-only -Wall -Wno-fatal -Wno-DECLFILENAME --timing freq_counter_7seg_ulx3s.v tb_freq_counter_7seg_ulx3s.vSin errores fatales de Verilog
Síntesisyosys -q -p "read_verilog freq_counter_7seg_ulx3s.v; synth_ecp5 -top freq_counter_7seg_ulx3s -json build/freq_counter.json"Se crea el netlist JSON
Place and routenextpnr-ecp5 --85k --package CABGA381 --speed 6 --json build/freq_counter.json --lpf ulx3s_freq_counter.lpf --textcfg build/freq_counter.configSe crea la configuración ECP5 rutada
Empaquetadoecppack build/freq_counter.config build/freq_counter.bitSe crea el bitstream
ProgramaciónopenFPGALoader -b ulx3s build/freq_counter.bitLa ULX3S queda configurada
mkdir -p build
verilator --lint-only -Wall -Wno-fatal -Wno-DECLFILENAME --timing freq_counter_7seg_ulx3s.v tb_freq_counter_7seg_ulx3s.v
yosys -q -p "read_verilog freq_counter_7seg_ulx3s.v; synth_ecp5 -top freq_counter_7seg_ulx3s -json build/freq_counter.json"
nextpnr-ecp5 --85k --package CABGA381 --speed 6 --json build/freq_counter.json --lpf ulx3s_freq_counter.lpf --textcfg build/freq_counter.config
ecppack build/freq_counter.config build/freq_counter.bit
openFPGALoader -b ulx3s build/freq_counter.bit

Validación paso a paso

  1. Comprobación de simulación/lint: Ejecuta el comando de Verilator. La condición de paso esperada es no obtener errores fatales de parser, temporización o conexión de módulos.
  2. Comprobación de síntesis: Ejecuta el comando de Yosys. La condición de paso es que exista build/freq_counter.json y que el módulo superior sea freq_counter_7seg_ulx3s.
  3. Comprobación de implementación: Ejecuta nextpnr-ecp5 y ecppack. La condición de paso es obtener build/freq_counter.bit.
  4. Comprobación en hardware: Programa la ULX3S, aplica una señal lenta de pulsos de 3,3 V y confirma que el número del display se actualiza al ritmo de la ventana de medida.
  5. Comprobación de referencia: Compara el display con una fuente conocida de 10 Hz o 100 Hz. La condición de paso es una lectura estable que coincida con la fuente dentro de un conteo en el borde de ventana.

Troubleshooting

SíntomaCausa probableSolución
El display queda apagadoLa polaridad de habilitación de dígitos no coincide con el móduloInvierte digit_en o verifica si el display es ánodo común
La cuenta siempre es ceroLa fuente de pulsos no comparte referencia con la FPGAComparte masa o usa una etapa de entrada aislada y segura
La cuenta es inestableLa entrada rebota o tiene demasiado ruidoAñade acondicionamiento externo, disparador Schmitt o filtrado digital más largo
La programación fallaLa placa no se detecta por USB/JTAGRevisa cable, permisos y soporte de openFPGALoader -b ulx3s
Se iluminan segmentos equivocadosEl orden de segmentos de tu PMOD es distintoReasigna seg[6:0] en el LPF o en la asignación seg

Mejoras

  • Rango de medida: Añade ventanas seleccionables de 0,1 s, 1 s y 10 s, y escala el valor mostrado según corresponda.
  • Calidad de señal: Añade una ruta digital de antirrebote/filtro para sensores mecánicos o un indicador de timeout cuando no llegan pulsos.
  • Interfaz de usuario: Añade punto decimal, indicador de overflow y salida UART de depuración para registrar medidas en un ordenador.

Checklist

  • La entrada de pulsos está limitada a lógica de 3,3 V.
  • La lista de pines LPF coincide con tu revisión de ULX3S y con el cableado PMOD.
  • Verilator y Yosys pasan antes de programar hardware.
  • nextpnr-ecp5, ecppack y openFPGALoader terminan sin errores.
  • El display se actualiza con una fuente de pulsos de referencia conocida.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué placa se utiliza para construir el contador de frecuencia descrito?




Pregunta 2: ¿Qué tipo de señal de entrada cuenta el sistema?




Pregunta 3: ¿Dónde se muestra la tasa medida?




Pregunta 4: ¿Para qué caso de uso se menciona explícitamente este proyecto?




Pregunta 5: ¿Qué tipo de flanco se cuenta en la entrada de pulsos?




Pregunta 6: ¿Qué debe hacerse con la entrada de pulsos respecto al reloj de la FPGA?




Pregunta 7: ¿Qué tipo de proyecto didáctico representa según el texto?




Pregunta 8: ¿Qué señal puede compararse con la cuenta mostrada para verificar cableado y tiempos?




Pregunta 9: ¿Qué dispositivo se menciona como ejemplo de señal a medir en el banco?




Pregunta 10: ¿Qué ventaja se menciona tras cargar el bitstream?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio