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,ecppackyopenFPGALoader. - 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
| Elemento | Modelo / señal exacta | Propósito |
|---|---|---|
| Placa FPGA | Radiona ULX3S, Lattice ECP5-85F | Lógica de temporización, conteo y display |
| Entrada de pulsos | Onda cuadrada 3,3 V, sensor antirrebote o canal de encoder | Señal que se mide |
| Display | PMOD de cuatro dígitos de siete segmentos, estilo cátodo común | Lectura local de frecuencia/cuenta |
| Cable USB | Conexión USB/JTAG de ULX3S | Programación y alimentación durante el laboratorio |
| Fuente opcional | Generador de funciones o salida de microcontrolador 3,3 V | Pulsos 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 ULX3S | Conecta con | Notas |
|---|---|---|
clk | Reloj de 25 MHz de ULX3S | Oscilador de placa |
rst_n | Pulsador o entrada con pull-up | Reset activo a nivel bajo |
pulse_in | Fuente de pulsos 3,3 V | Nunca superar 3,3 V |
seg[6:0] | Líneas a..g del display | Activas a nivel alto en este ejemplo |
digit_en[3:0] | Líneas de habilitación de dígito | Dígitos activos a nivel bajo |
GND | Masa de fuente de pulsos y display | Referencia 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
// ...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
| Paso | Comando | Resultado esperado |
|---|---|---|
| Lint | verilator --lint-only -Wall -Wno-fatal -Wno-DECLFILENAME --timing freq_counter_7seg_ulx3s.v tb_freq_counter_7seg_ulx3s.v | Sin errores fatales de Verilog |
| Síntesis | yosys -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 route | nextpnr-ecp5 --85k --package CABGA381 --speed 6 --json build/freq_counter.json --lpf ulx3s_freq_counter.lpf --textcfg build/freq_counter.config | Se crea la configuración ECP5 rutada |
| Empaquetado | ecppack build/freq_counter.config build/freq_counter.bit | Se crea el bitstream |
| Programación | openFPGALoader -b ulx3s build/freq_counter.bit | La 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
- 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.
- Comprobación de síntesis: Ejecuta el comando de Yosys. La condición de paso es que exista
build/freq_counter.jsony que el módulo superior seafreq_counter_7seg_ulx3s. - Comprobación de implementación: Ejecuta
nextpnr-ecp5yecppack. La condición de paso es obtenerbuild/freq_counter.bit. - 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.
- 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íntoma | Causa probable | Solución |
|---|---|---|
| El display queda apagado | La polaridad de habilitación de dígitos no coincide con el módulo | Invierte digit_en o verifica si el display es ánodo común |
| La cuenta siempre es cero | La fuente de pulsos no comparte referencia con la FPGA | Comparte masa o usa una etapa de entrada aislada y segura |
| La cuenta es inestable | La entrada rebota o tiene demasiado ruido | Añade acondicionamiento externo, disparador Schmitt o filtrado digital más largo |
| La programación falla | La placa no se detecta por USB/JTAG | Revisa cable, permisos y soporte de openFPGALoader -b ulx3s |
| Se iluminan segmentos equivocados | El orden de segmentos de tu PMOD es distinto | Reasigna 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,ecppackyopenFPGALoaderterminan 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
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.




