Caso práctico: entrenador de tiempo de reacción con ULX3S

Entrenador de tiempo de reacción con ULX3S

Objetivo y caso de uso

Qué construirás: Un entrenador de tiempo de reacción de hardware autónomo que utiliza un Registro de Desplazamiento con Retroalimentación Lineal (LFSR) para retardos aleatorios y un temporizador de hardware de precisión para medir la velocidad de los reflejos humanos.

Por qué es importante / Casos de uso

  • Prototipado de hardware: Sirve como un demostrador de hardware base para objetivos de reflejos personalizados o temporizadores de eventos que requieren precisión a nivel de milisegundos.
  • Pruebas de humanos en el bucle (Human-in-the-loop): Actúa como una herramienta educativa repetible para demostrar cómo la distracción afecta los tiempos de reacción en un entorno de aficionado controlado.
  • Validación de hardware: Ofrece una alternativa determinista a los temporizadores basados en software. Método de validación: La afirmación de precisión (precisión de 40 ns) se valida ejecutando el banco de pruebas de Verilator incluido e inspeccionando la forma de onda VCD resultante. Evidencia esperada: GTKWave mostrará un delta exacto de 40 ns (un ciclo de reloj de 25 MHz) entre el tic del temporizador interno y la actualización del registro de salida LED, demostrando cero fluctuación (jitter) inducida por el software.
  • Educación sobre lógica integrada: Demuestra la integración práctica de máquinas de estados de hardware y pseudoaleatoriedad a través de LFSRs.

Resultado esperado

  • Un sistema de medición de reflejos completamente funcional capaz de lograr una precisión de temporización determinista de hasta 40 ns por tic.
  • Simulaciones de formas de onda de Verilator verificadas que confirman la ausencia de latencia inducida por software.

Nota de seguridad: Este proyecto es un prototipo estrictamente educativo. No está diseñado, previsto ni certificado para evaluaciones de salud, evaluaciones de preparación críticas para la seguridad o cualquier aplicación profesional de monitoreo humano.

Audiencia: Estudiantes de sistemas embebidos, desarrolladores de FPGA e ingenieros de prototipado de hardware; Nivel: Intermedio

Arquitectura/flujo: Un LFSR genera un período de espera pseudoaleatorio → El estímulo se activa → El contador de hardware de 25 MHz comienza a contar a intervalos de 40 ns → La entrada del usuario detiene el contador → Se calcula y se muestra el tiempo de reacción determinista.

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: 3 apartados, 1 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 proyecto es un prototipo educativo, no un producto certificado. Antes de encender la configuración, verifique el pinout de la revisión exacta de su placa ULX3S, mantenga las señales de E/S de la FPGA a 3.3 V, nunca conecte 5 V directamente a los pines de E/S, desconecte la alimentación antes de cambiar el cableado y use fuentes externas adecuadas para cargas, motores o servos, compartiendo la tierra solo cuando el cableado lo requiera.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

Un LFSR genera un período de espera pseud…

El estímulo se activa

El contador de hardware de 25 MHz comienz…

La entrada del usuario detiene el contador

Se calcula y se muestra el tiempo de reac…

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Ruta de validación

Código fuente

Verilator

Yosys

Implementación hardware

Resumen conceptual de las herramientas usadas para comprobar el material publicado.

Requisitos previos

Para compilar, simular y grabar este proyecto, necesita la cadena de herramientas canónica de código abierto ECP5 instalada en su estación de trabajo Linux o macOS:
* Verilator: Para el análisis estático (linting) y la simulación del diseño Verilog.
* Yosys: Para sintetizar el código Verilog en RTL.
* nextpnr-ecp5: Para la ubicación y ruteo del diseño sintetizado para la arquitectura Lattice ECP5.
* Project Trellis (ecppack): Para empaquetar el diseño ruteado en un bitstream .bit grabable.
* openFPGALoader: Para programar el bitstream en la placa Radiona ULX3S.
* Make/Bash: Para ejecutar los comandos de compilación.

Materiales

  • Placa FPGA: Radiona ULX3S (específicamente la variante Lattice ECP5-85F, aunque el código es compatible con las variantes 12F y 45F si se ajusta la bandera del dispositivo en nextpnr).
  • Cable: Un cable de datos USB Tipo C estándar para alimentación y programación.

Configuración y asignación de pines

Dado que este proyecto depende completamente de los periféricos integrados en la Radiona ULX3S, no es necesario ningún cableado externo. Las conexiones se definen lógicamente en el archivo de restricciones.

Nombre de la señalPin de ULX3STipo de E/SDescripción
clk_25mhzG2LVCMOS33Oscilador integrado de 25 MHz
btn_startR1LVCMOS33Botón arriba (btn[1]), Activo en alto
btn_reactT1LVCMOS33Botón abajo (btn[2]), Activo en alto
led[0]B2LVCMOS33LED 0 (LSB)
led[1]C2LVCMOS33LED 1
led[2]C1LVCMOS33LED 2
led[3]D2LVCMOS33LED 3
led[4]D1LVCMOS33LED 4
led[5]E2LVCMOS33LED 5
led[6]E1LVCMOS33LED 6
led[7]H3LVCMOS33LED 7 (MSB)

El flujo lógico se basa en una Máquina de Estados Finitos (FSM):
* Estado IDLE (Inactivo): Los LEDs exteriores parpadean para indicar que está listo. Esperando a btn_start.
* Estado WAIT (Espera): Los LEDs se apagan. El sistema espera una duración aleatoria (entre 1 y ~4 segundos) generada por un LFSR interno. Si se presiona btn_react en este momento, se desencadena un estado de FAULT (Fallo).
* Estado TEST (Prueba): Todos los LEDs se encienden. Comienza un contador de milisegundos. El sistema espera a btn_react.
* Estado RESULT (Resultado): El tiempo de reacción se muestra en el gráfico de barras LED. Cada LED iluminado representa un bloque de 50 ms a partir de 100 ms.
* Estado FAULT (Fallo): Los LEDs parpadean rápidamente para indicar una salida en falso. Esperando a btn_start para reiniciar.

Archivos fuente del proyecto

El proyecto consta de cuatro archivos: el módulo Verilog sintetizable, el banco de pruebas Verilog para la simulación, el archivo de restricciones LPF y un script de compilación en Bash. Manténgalos en el mismo directorio de trabajo.

1. Verilog sintetizable: reaction_trainer.v

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

/*
 * Module: reaction_trainer
 * Description: A hardware reaction time measurement tool for the Radiona ULX3S.
 */
module reaction_trainer #(
    parameter MS_TICKS = 25000,      // 25MHz clock: 25,000 ticks = 1ms
    parameter DEBOUNCE_TICKS = 250000 // 10ms debounce
)(
    input  wire clk_25mhz,
    input  wire btn_start,
    input  wire btn_react,
    output reg  [7:0] led
);

    // --- State Machine Definitions ---
    localparam S_IDLE   = 3'd0;
    localparam S_WAIT   = 3'd1;
    localparam S_TEST   = 3'd2;
    localparam S_RESULT = 3'd3;
    localparam S_FAULT  = 3'd4;

    reg [2:0] state = S_IDLE;

    // --- Button Debouncing ---
    reg [17:0] debounce_counter_start = 0;
    reg [17:0] debounce_counter_react = 0;
    reg start_clean = 0;
    reg react_clean = 0;
    reg start_sync_1 = 0, start_sync_2 = 0;
    reg react_sync_1 = 0, react_sync_2 = 0;

    always @(posedge clk_25mhz) begin
        // Double-flop synchronizers
        start_sync_1 <= btn_start; start_sync_2 <= start_sync_1;
        react_sync_1 <= btn_react; react_sync_2 <= react_sync_1;

        // Debounce btn_start
        if (start_sync_2 == start_clean) begin
            debounce_counter_start <= 0;
        end else begin
            debounce_counter_start <= debounce_counter_start + 1;
            if (debounce_counter_start >= DEBOUNCE_TICKS) begin
                start_clean <= start_sync_2;
                debounce_counter_start <= 0;
            end
        end

        // Debounce btn_react
        if (react_sync_2 == react_clean) begin
            debounce_counter_react <= 0;
        end else begin
            debounce_counter_react <= debounce_counter_react + 1;
            if (debounce_counter_react >= DEBOUNCE_TICKS) begin
                react_clean <= react_sync_2;
                debounce_counter_react <= 0;
            end
        end
    end

    // Edge detection for debounced buttons
    reg start_clean_last = 0;
    reg react_clean_last = 0;
    wire start_pressed = (start_clean && !start_clean_last);
    wire react_pressed = (react_clean && !react_clean_last);

    always @(posedge clk_25mhz) begin
        start_clean_last <= start_clean;
        react_clean_last <= react_clean;
    end

    // --- Timers and LFSR ---
    reg [15:0] ms_counter = 0;
    reg [15:0] timer_ms = 0;
    reg [15:0] random_wait_ms = 0;

    // 16-bit LFSR for pseudo-randomness
    reg [15:0] lfsr = 16'hACE1;

    always @(posedge clk_25mhz) begin
        // Continuously run LFSR to ensure entropy based on human timing
        lfsr <= {lfsr[14:0], lfsr[15] ^ lfsr[13] ^ lfsr[12] ^ lfsr[10]};
    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: reaction_trainer
 * Description: A hardware reaction time measurement tool for the Radiona ULX3S.
 */
module reaction_trainer #(
    parameter MS_TICKS = 25000,      // 25MHz clock: 25,000 ticks = 1ms
    parameter DEBOUNCE_TICKS = 250000 // 10ms debounce
)(
    input  wire clk_25mhz,
    input  wire btn_start,
    input  wire btn_react,
    output reg  [7:0] led
);

    // --- State Machine Definitions ---
    localparam S_IDLE   = 3'd0;
    localparam S_WAIT   = 3'd1;
    localparam S_TEST   = 3'd2;
    localparam S_RESULT = 3'd3;
    localparam S_FAULT  = 3'd4;

    reg [2:0] state = S_IDLE;

    // --- Button Debouncing ---
    reg [17:0] debounce_counter_start = 0;
    reg [17:0] debounce_counter_react = 0;
    reg start_clean = 0;
    reg react_clean = 0;
    reg start_sync_1 = 0, start_sync_2 = 0;
    reg react_sync_1 = 0, react_sync_2 = 0;

    always @(posedge clk_25mhz) begin
        // Double-flop synchronizers
        start_sync_1 <= btn_start; start_sync_2 <= start_sync_1;
        react_sync_1 <= btn_react; react_sync_2 <= react_sync_1;

        // Debounce btn_start
        if (start_sync_2 == start_clean) begin
            debounce_counter_start <= 0;
        end else begin
            debounce_counter_start <= debounce_counter_start + 1;
            if (debounce_counter_start >= DEBOUNCE_TICKS) begin
                start_clean <= start_sync_2;
                debounce_counter_start <= 0;
            end
        end

        // Debounce btn_react
        if (react_sync_2 == react_clean) begin
            debounce_counter_react <= 0;
        end else begin
            debounce_counter_react <= debounce_counter_react + 1;
            if (debounce_counter_react >= DEBOUNCE_TICKS) begin
                react_clean <= react_sync_2;
                debounce_counter_react <= 0;
            end
        end
    end

    // Edge detection for debounced buttons
    reg start_clean_last = 0;
    reg react_clean_last = 0;
    wire start_pressed = (start_clean && !start_clean_last);
    wire react_pressed = (react_clean && !react_clean_last);

    always @(posedge clk_25mhz) begin
        start_clean_last <= start_clean;
        react_clean_last <= react_clean;
    end

    // --- Timers and LFSR ---
    reg [15:0] ms_counter = 0;
    reg [15:0] timer_ms = 0;
    reg [15:0] random_wait_ms = 0;

    // 16-bit LFSR for pseudo-randomness
    reg [15:0] lfsr = 16'hACE1;

    always @(posedge clk_25mhz) begin
        // Continuously run LFSR to ensure entropy based on human timing
        lfsr <= {lfsr[14:0], lfsr[15] ^ lfsr[13] ^ lfsr[12] ^ lfsr[10]};
    end

    // --- Main FSM ---
    reg [24:0] blink_counter = 0; // For visual effects in IDLE/FAULT

    always @(posedge clk_25mhz) begin
        blink_counter <= blink_counter + 1;

        // Millisecond tick generator
        if (ms_counter >= MS_TICKS - 1) begin
            ms_counter <= 0;
            if (state == S_WAIT || state == S_TEST) begin
                timer_ms <= timer_ms + 1;
            end
        end else begin
            ms_counter <= ms_counter + 1;
        end

        case (state)
            S_IDLE: begin
                // Alternating outer LEDs to show IDLE
                led <= blink_counter[23] ? 8'b10000001 : 8'b01000010;
                if (start_pressed) begin
                    // Calculate wait time: 1000 ms + random (0 to 2047 ms)
                    random_wait_ms <= 1000 + (lfsr & 16'h07FF);
                    timer_ms <= 0;
                    ms_counter <= 0;
                    state <= S_WAIT;
                end
            end

            S_WAIT: begin
                led <= 8'b00000000; // All LEDs off during wait
                if (react_pressed) begin
                    // False start!
                    state <= S_FAULT;
                end else if (timer_ms >= random_wait_ms) begin
                    timer_ms <= 0;
                    state <= S_TEST;
                end
            end

            S_TEST: begin
                led <= 8'b11111111; // GO signal!
                if (react_pressed) begin
                    state <= S_RESULT;
                end else if (timer_ms > 5000) begin
                    // Timeout after 5 seconds
                    state <= S_IDLE;
                end
            end

            S_RESULT: begin
                // Display bar graph based on reaction time (timer_ms)
                // <150ms: 1 LED, 150-199: 2 LEDs, 200-249: 3 LEDs... >450ms: 8 LEDs
                if (timer_ms < 150)       led <= 8'b00000001;
                else if (timer_ms < 200)  led <= 8'b00000011;
                else if (timer_ms < 250)  led <= 8'b00000111;
                else if (timer_ms < 300)  led <= 8'b00001111;
                else if (timer_ms < 350)  led <= 8'b00011111;
                else if (timer_ms < 400)  led <= 8'b00111111;
                else if (timer_ms < 450)  led <= 8'b01111111;
                else                      led <= 8'b11111111;

                if (start_pressed) begin
                    state <= S_IDLE;
                end
            end

            S_FAULT: begin
                // Fast blink all LEDs to indicate false start
                led <= blink_counter[22] ? 8'b11111111 : 8'b00000000;
                if (start_pressed) begin
                    state <= S_IDLE;
                end
            end

            default: state <= S_IDLE;
        endcase
    end

endmodule

2. Banco de pruebas Verilog: reaction_trainer_tb.v

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

`timescale 1ns/1ps

module reaction_trainer_tb;

    reg clk;
    reg btn_start;
    reg btn_react;
    wire [7:0] led;

    // Instantiate the Device Under Test (DUT)
    // Override parameters to speed up simulation drastically
    reaction_trainer #(
        .MS_TICKS(2),      // 2 ticks = 1 "ms" for simulation
        .DEBOUNCE_TICKS(5) // 5 ticks for debounce
    ) dut (
        .clk_25mhz(clk),
        .btn_start(btn_start),
        .btn_react(btn_react),
        .led(led)
    );

    // Generate simulated clock
    initial begin
        clk = 0;
        forever #20 clk = ~clk; // 40ns period -> 25MHz
    end

    initial begin
        $dumpfile("reaction_trainer.vcd");
        $dumpvars(0, reaction_trainer_tb);

        // Initialize inputs
// ...

🔒 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
`timescale 1ns/1ps

module reaction_trainer_tb;

    reg clk;
    reg btn_start;
    reg btn_react;
    wire [7:0] led;

    // Instantiate the Device Under Test (DUT)
    // Override parameters to speed up simulation drastically
    reaction_trainer #(
        .MS_TICKS(2),      // 2 ticks = 1 "ms" for simulation
        .DEBOUNCE_TICKS(5) // 5 ticks for debounce
    ) dut (
        .clk_25mhz(clk),
        .btn_start(btn_start),
        .btn_react(btn_react),
        .led(led)
    );

    // Generate simulated clock
    initial begin
        clk = 0;
        forever #20 clk = ~clk; // 40ns period -> 25MHz
    end

    initial begin
        $dumpfile("reaction_trainer.vcd");
        $dumpvars(0, reaction_trainer_tb);

        // Initialize inputs
        btn_start = 0;
        btn_react = 0;

        // Wait a few cycles in IDLE
        #1000;

        // Press START button
        $display("[%0t] Pressing START button...", $time);
        btn_start = 1;
        #500;
        btn_start = 0;

        // Wait for LED to turn on (TEST state)
        $display("[%0t] Waiting for GO signal...", $time);
        wait(led == 8'b11111111);
        $display("[%0t] LED is ON! Reacting...", $time);

        // Simulate a fast reaction time (e.g., 180ms)
        // In this TB, 1ms = 2 ticks = 80ns. So 180ms = 14400ns.
        #14400;
        btn_react = 1;
        #500;
        btn_react = 0;

        // Wait for RESULT state to settle
        #5000;
        $display("[%0t] Simulation complete. LED output: %b", $time, led);
        $finish;
    end

endmodule

3. Restricciones de la placa: ulx3s.lpf

LOCATE COMP "clk_25mhz" SITE "G2";
IOBUF PORT "clk_25mhz" PULLMODE=NONE IO_TYPE=LVCMOS33;

LOCATE COMP "btn_start" SITE "R1";
IOBUF PORT "btn_start" PULLMODE=DOWN IO_TYPE=LVCMOS33;

LOCATE COMP "btn_react" SITE "T1";
IOBUF PORT "btn_react" PULLMODE=DOWN IO_TYPE=LVCMOS33;

LOCATE COMP "led[0]" SITE "B2";
LOCATE COMP "led[1]" SITE "C2";
LOCATE COMP "led[2]" SITE "C1";
LOCATE COMP "led[3]" SITE "D2";
LOCATE COMP "led[4]" SITE "D1";
LOCATE COMP "led[5]" SITE "E2";
LOCATE COMP "led[6]" SITE "E1";
LOCATE COMP "led[7]" SITE "H3";

IOBUF PORT "led[0]" PULLMODE=NONE IO_TYPE=LVCMOS33;
IOBUF PORT "led[1]" PULLMODE=NONE IO_TYPE=LVCMOS33;
IOBUF PORT "led[2]" PULLMODE=NONE IO_TYPE=LVCMOS33;
IOBUF PORT "led[3]" PULLMODE=NONE IO_TYPE=LVCMOS33;
IOBUF PORT "led[4]" PULLMODE=NONE IO_TYPE=LVCMOS33;
IOBUF PORT "led[5]" PULLMODE=NONE IO_TYPE=LVCMOS33;
IOBUF PORT "led[6]" PULLMODE=NONE IO_TYPE=LVCMOS33;
IOBUF PORT "led[7]" PULLMODE=NONE IO_TYPE=LVCMOS33;

4. Script de compilación: build.sh

#!/bin/bash
set -e

echo "Linting and Simulating with Verilator..."
verilator --lint-only reaction_trainer.v

echo "Synthesizing with Yosys..."
yosys -p "synth_ecp5 -json reaction_trainer.json" reaction_trainer.v

echo "Place and Route with nextpnr..."
nextpnr-ecp5 --85k --json reaction_trainer.json --lpf ulx3s.lpf --textcfg reaction_trainer_out.config

echo "Packing bitstream..."
ecppack reaction_trainer_out.config reaction_trainer.bit

echo "Build complete. To flash, run:"
echo "openFPGALoader --board=ulx3s reaction_trainer.bit"

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é tipo de sistema se va a construir según el artículo?




Pregunta 2: ¿Qué componente se utiliza para generar retardos aleatorios en el sistema?




Pregunta 3: ¿Cuál es el propósito principal del temporizador de hardware en este proyecto?




Pregunta 4: ¿Para qué sirve este proyecto en el ámbito del prototipado de hardware?




Pregunta 5: En las pruebas de 'humanos en el bucle', ¿qué demuestra esta herramienta educativa?




Pregunta 6: ¿Qué ventaja ofrece este sistema frente a los temporizadores basados en software?




Pregunta 7: ¿Cuál es la precisión afirmada del temporizador de hardware?




Pregunta 8: ¿Qué herramienta se utiliza para validar la afirmación de precisión del sistema?




Pregunta 9: ¿Qué programa se menciona para inspeccionar la forma de onda VCD resultante?




Pregunta 10: ¿A qué equivale el delta exacto de 40 ns mostrado en la validación?




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