Objetivo y caso de uso
Qué construirás: Construirás un pequeño panel de estado VGA gobernado por UART en la Radiona ULX3S. Un dispositivo serie envía R, Y o G, y la FPGA cambia el fondo del monitor a rojo, amarillo o verde mientras replica el estado en tres LEDs.
Para qué sirve
- Mostrar de forma visible el estado de una producción, laboratorio o banco de pruebas sin dejar un PC conectado a la FPGA.
- Convertir una salida UART simple de 3,3 V de otro controlador en un indicador local grande.
- Enseñar una ruta de datos FPGA completa: recepción UART, decodificación de estado, temporización VGA y salida visible para humanos.
- Dar al alumnado un display práctico de depuración que podrá reutilizar después en otros proyectos con ULX3S.
Resultado esperado
- El receptor UART captura un byte a 115200 baudios con el reloj de 25 MHz de la ULX3S.
- El byte
Rselecciona rojo,Yselecciona amarillo yGselecciona verde. - La sincronía VGA funciona continuamente y el área visible cambia de color según el último comando válido.
- El testbench usa un divisor UART reducido y verifica que recibir
Gactiva el estado verde.
Público y nivel: Estudiantes de FPGA de nivel medio que ya conocen registros y quieren combinar entrada serie con salida de vídeo.
Arquitectura/flujo en una línea: USB-UART -> receptor UART ULX3S -> registro de estado -> generador de temporización/color VGA -> monitor y LEDs.
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 indicador educativo de baja tensión. Usa solo señales UART de 3,3 V en los pines de E/S de la ULX3S. No conectes niveles RS-232, lógica de 5 V, cableado industrial ni equipos alimentados por red directamente a la placa. Usa un adaptador USB-UART, desplazador de nivel o aislador adecuado cuando la fuente no sea ya compatible con 3,3 V.
Prerrequisitos
- Radiona ULX3S con Lattice ECP5-85F.
- OSS CAD Suite con
verilator,yosys,nextpnr-ecp5,ecppackyopenFPGALoader. - Adaptador USB-UART de 3,3 V conectado al pin
uart_rxseleccionado y masa común. - PMOD VGA o adaptador VGA probado con ULX3S.
- Programa de terminal capaz de enviar caracteres sueltos a 115200 baudios.
Materiales
| Elemento | Modelo / señal exacta | Propósito |
|---|---|---|
| Placa FPGA | Radiona ULX3S, Lattice ECP5-85F | Decodificación UART y generación VGA |
| Adaptador serie | USB-UART 3,3 V, TX conectado al RX de FPGA | Envía comandos de estado |
| Salida de vídeo | PMOD VGA o cableado VGA ULX3S | Panel visible |
| LEDs | Tres LEDs de ULX3S o LEDs en PMOD | Réplica local del estado |
Setup/Conexión
Conecta la salida TX del adaptador a la entrada uart_rx de la FPGA, une las masas y deja el RX del adaptador sin conectar salvo que amplíes el diseño más adelante. Cablea el PMOD VGA según los pines LPF usados en tu laboratorio.
| Señal ULX3S | Conecta con | Notas |
|---|---|---|
clk | Reloj de placa de 25 MHz | Base de temporización de píxel y UART |
uart_rx | TX USB-UART, 3,3 V | Entrada de comandos serie |
vga_hsync, vga_vsync | Pines de sincronía VGA | Temporización 640×480 |
vga_r[3:0], vga_g[3:0], vga_b[3:0] | Pines de color VGA | Pantalla de estado de color sólido |
status_led[2:0] | LEDs | Réplica rojo/amarillo/verde |
Código validado
uart_vga_status_ulx3s.v
Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.
module uart_vga_status_ulx3s #(
parameter CLK_HZ = 25000000,
parameter BAUD = 115200,
parameter CLKS_PER_BIT = 217
) (
input wire clk,
input wire rst_n,
input wire uart_rx,
output reg vga_hsync,
output reg vga_vsync,
output reg [3:0] vga_r,
output reg [3:0] vga_g,
output reg [3:0] vga_b,
output reg [2:0] status_led
);
reg [9:0] h_count;
reg [9:0] v_count;
reg [1:0] status;
reg [1:0] rx_state;
reg [15:0] rx_clk_count;
reg [2:0] rx_bit_index;
reg [7:0] rx_shift;
reg [7:0] rx_byte;
reg rx_ready;
reg rx_sync_0;
reg rx_sync_1;
localparam RX_IDLE = 2'd0;
localparam RX_START = 2'd1;
localparam RX_DATA = 2'd2;
localparam RX_STOP = 2'd3;
wire visible = h_count < 10'd640 && v_count < 10'd480;
always @(posedge clk) begin
if (!rst_n) begin
h_count <= 10'd0;
v_count <= 10'd0;
end else if (h_count == 10'd799) begin
h_count <= 10'd0;
if (v_count == 10'd524) begin
v_count <= 10'd0;
end else begin
v_count <= v_count + 10'd1;
end
end else begin
h_count <= h_count + 10'd1;
end
end
always @(posedge clk) begin
if (!rst_n) begin
rx_state <= RX_IDLE;
rx_clk_count <= 16'd0;
rx_bit_index <= 3'd0;
rx_shift <= 8'd0;
rx_byte <= 8'd0;
rx_ready <= 1'b0;
rx_sync_0 <= 1'b1;
rx_sync_1 <= 1'b1;
end else begin
rx_sync_0 <= uart_rx;
rx_sync_1 <= rx_sync_0;
rx_ready <= 1'b0;
case (rx_state)
RX_IDLE: begin
rx_clk_count <= 16'd0;
rx_bit_index <= 3'd0;
if (!rx_sync_1) begin
rx_state <= RX_START;
end
end
// ...module uart_vga_status_ulx3s #(
parameter CLK_HZ = 25000000,
parameter BAUD = 115200,
parameter CLKS_PER_BIT = 217
) (
input wire clk,
input wire rst_n,
input wire uart_rx,
output reg vga_hsync,
output reg vga_vsync,
output reg [3:0] vga_r,
output reg [3:0] vga_g,
output reg [3:0] vga_b,
output reg [2:0] status_led
);
reg [9:0] h_count;
reg [9:0] v_count;
reg [1:0] status;
reg [1:0] rx_state;
reg [15:0] rx_clk_count;
reg [2:0] rx_bit_index;
reg [7:0] rx_shift;
reg [7:0] rx_byte;
reg rx_ready;
reg rx_sync_0;
reg rx_sync_1;
localparam RX_IDLE = 2'd0;
localparam RX_START = 2'd1;
localparam RX_DATA = 2'd2;
localparam RX_STOP = 2'd3;
wire visible = h_count < 10'd640 && v_count < 10'd480;
always @(posedge clk) begin
if (!rst_n) begin
h_count <= 10'd0;
v_count <= 10'd0;
end else if (h_count == 10'd799) begin
h_count <= 10'd0;
if (v_count == 10'd524) begin
v_count <= 10'd0;
end else begin
v_count <= v_count + 10'd1;
end
end else begin
h_count <= h_count + 10'd1;
end
end
always @(posedge clk) begin
if (!rst_n) begin
rx_state <= RX_IDLE;
rx_clk_count <= 16'd0;
rx_bit_index <= 3'd0;
rx_shift <= 8'd0;
rx_byte <= 8'd0;
rx_ready <= 1'b0;
rx_sync_0 <= 1'b1;
rx_sync_1 <= 1'b1;
end else begin
rx_sync_0 <= uart_rx;
rx_sync_1 <= rx_sync_0;
rx_ready <= 1'b0;
case (rx_state)
RX_IDLE: begin
rx_clk_count <= 16'd0;
rx_bit_index <= 3'd0;
if (!rx_sync_1) begin
rx_state <= RX_START;
end
end
RX_START: begin
if (rx_clk_count == (CLKS_PER_BIT / 2)) begin
rx_clk_count <= 16'd0;
rx_state <= rx_sync_1 ? RX_IDLE : RX_DATA;
end else begin
rx_clk_count <= rx_clk_count + 16'd1;
end
end
RX_DATA: begin
if (rx_clk_count == CLKS_PER_BIT - 1) begin
rx_clk_count <= 16'd0;
rx_shift[rx_bit_index] <= rx_sync_1;
if (rx_bit_index == 3'd7) begin
rx_state <= RX_STOP;
end else begin
rx_bit_index <= rx_bit_index + 3'd1;
end
end else begin
rx_clk_count <= rx_clk_count + 16'd1;
end
end
default: begin
if (rx_clk_count == CLKS_PER_BIT - 1) begin
rx_byte <= rx_shift;
rx_ready <= 1'b1;
rx_clk_count <= 16'd0;
rx_state <= RX_IDLE;
end else begin
rx_clk_count <= rx_clk_count + 16'd1;
end
end
endcase
end
end
always @(posedge clk) begin
if (!rst_n) begin
status <= 2'd0;
end else if (rx_ready) begin
if (rx_byte == 8'h52) begin
status <= 2'd0;
end else if (rx_byte == 8'h59) begin
status <= 2'd1;
end else if (rx_byte == 8'h47) begin
status <= 2'd2;
end
end
end
always @* begin
vga_hsync = ~((h_count >= 10'd656) && (h_count < 10'd752));
vga_vsync = ~((v_count >= 10'd490) && (v_count < 10'd492));
status_led = 3'b000;
vga_r = 4'h0;
vga_g = 4'h0;
vga_b = 4'h0;
if (visible) begin
if (status == 2'd0) begin
vga_r = 4'hf;
status_led = 3'b100;
end else if (status == 2'd1) begin
vga_r = 4'hf;
vga_g = 4'hc;
status_led = 3'b110;
end else begin
vga_g = 4'hf;
status_led = 3'b001;
end
end
end
endmodule
tb_uart_vga_status_ulx3s.v
`timescale 1ns/1ps
module tb_uart_vga_status_ulx3s;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg uart_rx = 1'b1;
wire vga_hsync;
wire vga_vsync;
wire [3:0] vga_r;
wire [3:0] vga_g;
wire [3:0] vga_b;
wire [2:0] status_led;
always #5 clk = ~clk;
uart_vga_status_ulx3s #(
.CLKS_PER_BIT(4)
) dut (
.clk(clk),
.rst_n(rst_n),
.uart_rx(uart_rx),
.vga_hsync(vga_hsync),
.vga_vsync(vga_vsync),
.vga_r(vga_r),
.vga_g(vga_g),
.vga_b(vga_b),
.status_led(status_led)
);
task uart_send;
input [7:0] value;
integer i;
begin
uart_rx = 1'b0;
repeat (4) @(posedge clk);
for (i = 0; i < 8; i = i + 1) begin
uart_rx = value[i];
repeat (4) @(posedge clk);
end
uart_rx = 1'b1;
repeat (8) @(posedge clk);
end
endtask
initial begin
repeat (4) @(posedge clk);
rst_n = 1'b1;
uart_send(8'h47);
repeat (12) @(posedge clk);
if (status_led != 3'b001) begin
$fatal(1, "Green status was not latched");
end
if (vga_g == 4'h0) begin
$fatal(1, "VGA green channel is not active");
end
$finish;
end
endmodule
ulx3s_uart_vga_status.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 "uart_rx" SITE "P1";
IOBUF PORT "uart_rx" IO_TYPE=LVCMOS33 PULLMODE=UP;
LOCATE COMP "vga_hsync" SITE "A1";
LOCATE COMP "vga_vsync" SITE "B1";
IOBUF PORT "vga_hsync" IO_TYPE=LVCMOS33;
IOBUF PORT "vga_vsync" IO_TYPE=LVCMOS33;
LOCATE COMP "status_led[0]" SITE "C1";
LOCATE COMP "status_led[1]" SITE "D1";
LOCATE COMP "status_led[2]" SITE "E1";
IOBUF PORT "status_led[0]" IO_TYPE=LVCMOS33;
IOBUF PORT "status_led[1]" IO_TYPE=LVCMOS33;
IOBUF PORT "status_led[2]" IO_TYPE=LVCMOS33;
Comandos de build/flash/run
| Paso | Comando | Resultado esperado |
|---|---|---|
| Lint | verilator --lint-only -Wall -Wno-fatal -Wno-DECLFILENAME --timing uart_vga_status_ulx3s.v tb_uart_vga_status_ulx3s.v | Verilog sin errores fatales |
| Síntesis | yosys -q -p "read_verilog uart_vga_status_ulx3s.v; synth_ecp5 -top uart_vga_status_ulx3s -json build/uart_vga_status.json" | Netlist JSON ECP5 |
| Ruteo | nextpnr-ecp5 --85k --package CABGA381 --speed 6 --json build/uart_vga_status.json --lpf ulx3s_uart_vga_status.lpf --textcfg build/uart_vga_status.config | Configuración rutada |
| Empaquetado | ecppack build/uart_vga_status.config build/uart_vga_status.bit | Archivo bitstream |
| Programación | openFPGALoader -b ulx3s build/uart_vga_status.bit | Placa configurada |
mkdir -p build
verilator --lint-only -Wall -Wno-fatal -Wno-DECLFILENAME --timing uart_vga_status_ulx3s.v tb_uart_vga_status_ulx3s.v
yosys -q -p "read_verilog uart_vga_status_ulx3s.v; synth_ecp5 -top uart_vga_status_ulx3s -json build/uart_vga_status.json"
nextpnr-ecp5 --85k --package CABGA381 --speed 6 --json build/uart_vga_status.json --lpf ulx3s_uart_vga_status.lpf --textcfg build/uart_vga_status.config
ecppack build/uart_vga_status.config build/uart_vga_status.bit
openFPGALoader -b ulx3s build/uart_vga_status.bit
Validación paso a paso
- Ejecuta el lint con Verilator y confirma que no aparecen errores fatales de sintaxis UART/VGA.
- Ejecuta Yosys y confirma que
build/uart_vga_status.jsonse crea solo desde el archivo sintetizable. - Rutea y empaqueta el bitstream con el LPF usado en tu cableado.
- Programa la ULX3S, abre un terminal serie a 115200 baudios y envía
R,YyG. - Confirma que el fondo del monitor y los LEDs siguen el último byte válido.
Troubleshooting
| Síntoma | Causa probable | Solución |
|---|---|---|
| Hay sincronía pero color incorrecto | El byte UART no llega | Revisa baud rate, masa y cable TX-adaptador a RX-FPGA |
| No hay imagen VGA | El mapa de pines PMOD no coincide | Actualiza el LPF para tu conector |
| Cambios de estado aleatorios | RX UART está flotante | Mantén el pull-up y conecta un adaptador real de 3,3 V |
| Falla la carga | USB/JTAG no disponible | Revisa openFPGALoader -b ulx3s y permisos de cable |
Mejoras
- Añade un pequeño generador de texto para mostrar
RUN,WAIToSTOP. - Añade un transmisor UART de eco para depurar desde el terminal.
- Añade un timeout que ponga la pantalla azul si no llega ningún comando durante varios segundos.
Checklist
- El adaptador UART es compatible con 3,3 V.
- Los pines LPF coinciden con el cableado ULX3S.
- Verilator y Yosys pasan antes de programar.
- La sincronía VGA aparece en el monitor.
- Enviar
R,YyGcambia el estado mostrado.
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.




