Objetivo y caso de uso
Qué construirás: Un motor de sprites VGA que permita mostrar gráficos animados en una pantalla a 800×600 utilizando la FPGA Digilent Basys 3.
Para qué sirve
- Desarrollar videojuegos simples que se ejecuten en hardware FPGA.
- Visualizar datos en tiempo real utilizando gráficos en una pantalla VGA.
- Implementar animaciones fluidas para aplicaciones educativas en diseño digital.
Resultado esperado
- Capacidad de mostrar al menos 60 FPS en la salida VGA.
- Latencia de entrada/salida inferior a 10 ms al cambiar sprites.
- Uso de menos de 50% de la lógica disponible en la FPGA Artix-7.
Público objetivo: Ingenieros y estudiantes de electrónica; Nivel: Avanzado
Arquitectura/flujo: Implementación de Verilog en la FPGA para gestionar temporización y salida VGA.
Nivel: Avanzado
Prerrequisitos
- Sistemas operativos soportados (cualquiera de los dos):
- Windows 11 Pro 22H2 (x64)
- Ubuntu 22.04.3 LTS (x86_64)
- Toolchain exacta:
- Xilinx Vivado Design Suite 2023.2 (edición WebPACK), incluyendo:
- Vivado 2023.2
- Hardware Server (hw_server) 2023.2
- Cable drivers para Digilent (instalados con Vivado)
- Requisitos de hardware del PC:
- CPU x86_64 con soporte SSE4
- 16 GB de RAM (mínimo recomendado para implementaciones con Artix‑7)
- 20 GB de espacio libre en disco para instalación + proyectos
Notas clave:
– Usaremos Verilog (subset conciso).
– El dispositivo exacto es el de la Digilent Basys 3: Xilinx Artix‑7 xc7a35tcpg236‑1.
– Usaremos únicamente CLI de Vivado (batch TCL). No se requiere generar IPs de Vivado: implementaremos el MMCM directamente con la primitiva MMCME2_BASE para obtener 40 MHz desde 100 MHz.
Materiales
- Digilent Basys 3 (modelo exacto).
- Cable micro‑USB (para alimentación y programación vía JTAG).
- Cable VGA macho‑macho.
- Monitor VGA que acepte 800×600@60 Hz (casi todos).
- Opcional:
- Sonda lógica/osciloscopio para observar HSYNC/VSYNC (no imprescindible).
- Teclado/ratón solo por comodidad en el PC.
- Imprescindible: internet únicamente para descarga de Vivado 2023.2 (si no está ya instalado).
Preparación y conexión
1) Configuración física:
– Coloca el jumper de alimentación JP1 en “USB”.
– Conecta el cable micro‑USB del PC a la Basys 3 (alimentación + JTAG).
– Conecta el cable VGA desde el conector VGA de la Basys 3 al monitor.
– Enciende la placa (switch de encendido lateral). El LED de “Power” debe encenderse.
2) Señales y puertos usados (mapa HDL → serigrafía de la Basys 3):
– Usaremos el archivo de constraints maestro de Digilent “Basys3_Master.xdc” (oficial), donde están los mapeos exactos de pines. No reproducimos aquí todos los pines; indicamos las señales a descomentar/activar en dicho XDC.
– Tabla de señales de nuestro diseño:
| Puerto HDL | Etiqueta en placa (Basys 3) | Descripción | Notas de constraints (en Basys3_Master.xdc) |
|---|---|---|---|
| clk_100mhz | CLK100MHZ | Reloj de 100 MHz a la FPGA | create_clock a 10.000 ns ya provisto |
| btnC, btnU, btnD, btnL, btnR | BTNC/BTNU/BTND/BTNL/BTNR | Botones de usuario | Entradas LVCMOS33 |
| sw[15:0] | SW15..SW0 | 16 interruptores deslizantes | Entradas LVCMOS33 |
| vga_r[3:0] | VGA_R[3:0] | Rojo (4 bits) | Salidas LVCMOS33 con resistencia DAC integrada |
| vga_g[3:0] | VGA_G[3:0] | Verde (4 bits) | Salidas LVCMOS33 |
| vga_b[3:0] | VGA_B[3:0] | Azul (4 bits) | Salidas LVCMOS33 |
| vga_hs | VGA_HS | Sincronía horizontal | Polaridad positiva (800×600@60) |
| vga_vs | VGA_VS | Sincronía vertical | Polaridad positiva (800×600@60) |
3) Edición del XDC:
– Copia “Basys3_Master.xdc” a ./constraints/Basys3_Master.xdc en el proyecto.
– Abre el archivo y:
– Descomenta las líneas del reloj “CLK100MHZ”.
– Descomenta las líneas para SW[15:0], BTNC/BTNU/BTND/BTNL/BTNR.
– Descomenta las líneas de las salidas VGA: VGA_R[3:0], VGA_G[3:0], VGA_B[3:0], VGA_HS, VGA_VS.
– Los nombres de puertos deben coincidir exactamente con los declarados en el toplevel: clk_100mhz, sw[15:0], btnC, btnU, btnD, btnL, btnR, vga_r[3:0], vga_g[3:0], vga_b[3:0], vga_hs, vga_vs.
Sugerencia práctica: El XDC de Digilent incluye comentarios con “##” para habilitar cada bloque; no cambies los pins, solo asegúrate de que los nombres de puertos HDL coinciden.
Código completo (Verilog) y explicación
A continuación incluimos todos los módulos necesarios:
– Generación de reloj (100 MHz → 40 MHz) con MMCME2_BASE (sin IP).
– Generador de timings para 800×600@60 Hz (pixclk = 40 MHz; HS/VS positivos).
– Motor de sprites simple con composición por prioridad y color clave (índice 0 transparente).
– Toplevel para Basys 3 que integra todo y anima sprites.
Módulos Verilog
Bloque 1/2: código HDL completo (módulos de reloj, timing VGA y motor de sprites).
// clkgen_100to40.v
// Genera 40.000 MHz a partir de 100.000 MHz usando MMCME2_BASE.
// fVCO = 100 * 8 = 800 MHz; CLKOUT0 = 800 / 20 = 40.0 MHz.
// Requiere Artix-7 (7-series).
module clkgen_100to40(
input wire clk_100mhz,
input wire reset, // reset activo alto hacia el MMCM
output wire clk_40mhz,
output wire locked
);
wire clk_in_buf;
wire clkfb, clkfb_buf;
wire clk_out_mmcm;
// Buffer del reloj de 100 MHz
BUFG u_bufg_in (.I(clk_100mhz), .O(clk_in_buf));
// MMCM para generar 40 MHz
MMCME2_BASE #(
.BANDWIDTH("OPTIMIZED"),
.CLKIN1_PERIOD(10.0),
.CLKFBOUT_MULT_F(8.0),
.DIVCLK_DIVIDE(1),
.CLKOUT0_DIVIDE_F(20.0),
.CLKOUT0_PHASE(0.0),
.REF_JITTER1(0.010),
.STARTUP_WAIT("FALSE")
) u_mmcm (
.CLKIN1 (clk_in_buf),
.CLKFBIN (clkfb_buf),
.CLKFBOUT (clkfb),
.CLKOUT0 (clk_out_mmcm),
.LOCKED (locked),
.PWRDWN (1'b0),
.RST (reset)
);
// Buffers de realimentación y salida
BUFG u_bufg_fb (.I(clkfb), .O(clkfb_buf));
BUFG u_bufg_out0 (.I(clk_out_mmcm), .O(clk_40mhz));
endmodule
// vga_timing_800x600.v
// Genera temporización para 800x600@60 Hz, pixclk = 40 MHz.
// Polaritad HSYNC/VSYNC positiva.
module vga_timing_800x600(
input wire clk_pix,
input wire rst, // síncrono al reloj de píxel
output reg [10:0] hcount, // 0..1055
output reg [9:0] vcount, // 0..627
output reg hsync,
output reg vsync,
output wire de, // data enable (dentro de área activa)
output wire line_start,
output wire frame_start
);
// Timings VESA 800x600@60
localparam H_ACTIVE = 800;
localparam H_FP = 40;
localparam H_SYNC = 128;
localparam H_BP = 88;
localparam H_TOTAL = H_ACTIVE + H_FP + H_SYNC + H_BP; // 1056
localparam V_ACTIVE = 600;
localparam V_FP = 1;
localparam V_SYNC = 4;
localparam V_BP = 23;
localparam V_TOTAL = V_ACTIVE + V_FP + V_SYNC + V_BP; // 628
assign de = (hcount < H_ACTIVE) && (vcount < V_ACTIVE);
assign line_start = (hcount == 11'd0);
assign frame_start = (hcount == 11'd0) && (vcount == 10'd0);
always @(posedge clk_pix) begin
if (rst) begin
hcount <= 11'd0;
vcount <= 10'd0;
hsync <= 1'b0;
vsync <= 1'b0;
end else begin
// Contadores H/V
if (hcount == H_TOTAL - 1) begin
hcount <= 11'd0;
if (vcount == V_TOTAL - 1) begin
vcount <= 10'd0;
end else begin
vcount <= vcount + 10'd1;
end
end else begin
hcount <= hcount + 11'd1;
end
// HSYNC (positivo)
if ((hcount >= H_ACTIVE + H_FP) && (hcount < H_ACTIVE + H_FP + H_SYNC))
hsync <= 1'b1;
else
hsync <= 1'b0;
// VSYNC (positivo)
if ((vcount >= V_ACTIVE + V_FP) && (vcount < V_ACTIVE + V_FP + V_SYNC))
vsync <= 1'b1;
else
vsync <= 1'b0;
end
end
endmodule
// sprite_engine.v
// Motor de sprites simple, 4bpp (16 colores) con palette 12-bit RGB444.
// - Transparencia por clave: índice 0 = transparente.
// - N_SPRITES sprites de 32x32 píxeles sintetizados on-the-fly (sin BRAM).
// - Composición por prioridad: sprite 0 tiene menos prioridad que sprite N-1.
module sprite_engine #(
parameter integer N_SPRITES = 8,
parameter integer SPR_W = 32,
parameter integer SPR_H = 32
)(
input wire clk_pix,
input wire rst,
input wire [10:0] x, // hcount
input wire [9:0] y, // vcount
input wire de, // dentro de área activa
input wire [15:0] ctrl, // switches para variantes/colores
// Atributos: posiciones y enable por sprite
input wire [10:0] spr_x [0:N_SPRITES-1],
input wire [9:0] spr_y [0:N_SPRITES-1],
input wire [0:0] spr_en [0:N_SPRITES-1],
output reg [11:0] rgb // RGB444
);
// Paleta de 16 colores (RGB444)
reg [11:0] palette [0:15];
initial begin
palette[ 0] = 12'h000; // transparente (no dibuja)
palette[ 1] = 12'hF00; // rojo
palette[ 2] = 12'h0F0; // verde
palette[ 3] = 12'h00F; // azul
palette[ 4] = 12'hFF0; // amarillo
palette[ 5] = 12'hF0F; // magenta
palette[ 6] = 12'h0FF; // cian
palette[ 7] = 12'hFFF; // blanco
palette[ 8] = 12'hF80; // naranja
palette[ 9] = 12'h90F; // púrpura
palette[10] = 12'h0A8; // verde azulado
palette[11] = 12'h840; // marrón
palette[12] = 12'h888; // gris medio
palette[13] = 12'h08F; // azul claro
palette[14] = 12'hF08; // rosa
palette[15] = 12'h4F4; // verde claro
end
// Fondo: patrón de cuadrícula y barras a partir de (x,y)
wire [3:0] bg_idx;
assign bg_idx = (x[6] ^ y[6]) ? {1'b0, x[5:4]} : {1'b0, y[5:4]};
wire [11:0] bg_rgb = palette[{1'b0, bg_idx[2:0]}];
// Función que sintetiza el pixel de un sprite "s" dado (índice 4-bit)
function [3:0] sprite_pix;
input integer sid;
input [5:0] sx; // rango 0..63, usaremos hasta 31
input [5:0] sy; // rango 0..63
reg [5:0] cx, cy;
reg [6:0] rsq;
begin
cx = sx - (SPR_W[5:0] >> 1);
cy = sy - (SPR_H[5:0] >> 1);
rsq = cx*cx + cy*cy;
case (sid % 8)
0: begin
// Círculo sólido
sprite_pix = (rsq < 15*15) ? 4'd1 : 4'd0;
end
1: begin
// Anillo
sprite_pix = ((rsq > 12*12) && (rsq < 15*15)) ? 4'd2 : 4'd0;
end
2: begin
// Rayas diagonales
sprite_pix = (((sx + sy) & 3) == 0) ? 4'd3 : 4'd0;
end
3: begin
// Checker 4x4
sprite_pix = (((sx[2]^sy[2])==1'b1) ? 4'd4 : 4'd0);
end
4: begin
// Triángulo
sprite_pix = (sy >= sx) ? 4'd5 : 4'd0;
end
5: begin
// Borde cuadrado
sprite_pix = ((sx==0)||(sy==0)||(sx==SPR_W-1)||(sy==SPR_H-1)) ? 4'd6 : 4'd0;
end
6: begin
// Cruz
sprite_pix = ((sx==SPR_W>>1)||(sy==SPR_H>>1)) ? 4'd7 : 4'd0;
end
default: begin
// Gradiente
sprite_pix = { (sx[4]^sy[4]), sx[3], sy[3], 1'b1 };
end
endcase
end
endfunction
integer i;
reg [11:0] rgb_next;
always @(posedge clk_pix) begin
if (rst) begin
rgb <= 12'h000;
end else begin
// Color base = fondo
rgb_next = bg_rgb;
if (de) begin
// Composición por prioridad: recorre todos los sprites
for (i = 0; i < N_SPRITES; i = i + 1) begin : compose
if (spr_en[i]) begin
if ((x >= spr_x[i]) && (x < spr_x[i] + SPR_W) &&
(y >= spr_y[i]) && (y < spr_y[i] + SPR_H)) begin
// Coordenadas locales dentro del sprite
// (los casts son seguros por tamaño)
reg [5:0] sx = x - spr_x[i];
reg [5:0] sy = y - spr_y[i];
reg [3:0] idx;
idx = sprite_pix(i, sx, sy);
if (idx != 4'd0) begin
// Variante de color conmutada por switches
// (permuta ligera de la paleta con ctrl)
reg [3:0] idx2;
idx2 = idx ^ ctrl[3:0];
rgb_next = palette[idx2];
end
end
end
end
rgb <= rgb_next;
end else begin
// Fuera de área activa: negro
rgb <= 12'h000;
end
end
end
endmodule
Bloque 2/2: toplevel para Basys 3, integración y animación.
// top_basys3.v
// Toplevel para Digilent Basys 3 (Artix-7 xc7a35tcpg236-1).
// Objetivo: vga-sprite-engine-800x600 (pixclk 40 MHz, HS/VS positivos).
module top_basys3(
input wire clk_100mhz,
input wire [15:0] sw,
input wire btnC,
input wire btnU,
input wire btnD,
input wire btnL,
input wire btnR,
output wire [3:0] vga_r,
output wire [3:0] vga_g,
output wire [3:0] vga_b,
output wire vga_hs,
output wire vga_vs
);
// Reloj de píxel: 40 MHz y señal locked
wire clk_pix, pll_locked;
wire rst_sync;
assign rst_sync = ~pll_locked | btnC; // reset global activo si no locked o botón central
clkgen_100to40 u_clkgen (
.clk_100mhz(clk_100mhz),
.reset (btnC), // reset al MMCM desde BTN C (activo alto)
.clk_40mhz (clk_pix),
.locked (pll_locked)
);
// Timings VGA 800x600@60
wire [10:0] x;
wire [9:0] y;
wire de;
wire hsync, vsync;
wire line_start, frame_start;
vga_timing_800x600 u_tmg (
.clk_pix (clk_pix),
.rst (rst_sync),
.hcount (x),
.vcount (y),
.hsync (hsync),
.vsync (vsync),
.de (de),
.line_start (line_start),
.frame_start(frame_start)
);
// Atributos de sprites: 8 sprites con movimiento autónomo (rebote)
localparam N = 8;
localparam SW = 32;
localparam SH = 32;
// Posiciones y enables
reg [10:0] spr_x [0:N-1];
reg [9:0] spr_y [0:N-1];
reg [0:0] spr_en[0:N-1];
// Velocidades (signed pequeños)
reg signed [3:0] vx [0:N-1];
reg signed [3:0] vy [0:N-1];
integer i;
initial begin
for (i = 0; i < N; i = i + 1) begin
spr_x[i] = 11'd50 + i*11'd60;
spr_y[i] = 10'd40 + i*10'd40;
spr_en[i]= 1'b1;
vx[i] = (i[0]) ? 4'sd2 : -4'sd2;
vy[i] = (i[1]) ? 4'sd1 : -4'sd1;
end
end
// Animación a fin de frame (60 Hz) o al pulsar botones para “empujar”
// sw[0] = pausa (1 = pausa)
// Botones: U/D/L/R aplican impulsos de velocidad al sprite 0
reg [15:0] ctrl_sw_sync;
always @(posedge clk_pix) begin
ctrl_sw_sync <= sw; // sincroniza switches
end
// Debounce/estrechamiento simple de botones (no crítico en demo)
reg [2:0] btnU_s, btnD_s, btnL_s, btnR_s;
always @(posedge clk_pix) begin
btnU_s <= {btnU_s[1:0], btnU};
btnD_s <= {btnD_s[1:0], btnD};
btnL_s <= {btnL_s[1:0], btnL};
btnR_s <= {btnR_s[1:0], btnR};
end
wire btnU_p = (btnU_s[2:1] == 2'b01);
wire btnD_p = (btnD_s[2:1] == 2'b01);
wire btnL_p = (btnL_s[2:1] == 2'b01);
wire btnR_p = (btnR_s[2:1] == 2'b01);
// Actualización de posiciones/velocidades
always @(posedge clk_pix) begin
if (rst_sync) begin
for (i = 0; i < N; i = i + 1) begin
spr_x[i] <= 11'd50 + i*11'd60;
spr_y[i] <= 10'd40 + i*10'd40;
spr_en[i] <= 1'b1;
vx[i] <= (i[0]) ? 4'sd2 : -4'sd2;
vy[i] <= (i[1]) ? 4'sd1 : -4'sd1;
end
end else begin
// Impulsos manuales al sprite 0
if (btnU_p) vy[0] <= vy[0] - 4'sd1;
if (btnD_p) vy[0] <= vy[0] + 4'sd1;
if (btnL_p) vx[0] <= vx[0] - 4'sd1;
if (btnR_p) vx[0] <= vx[0] + 4'sd1;
if (frame_start && !ctrl_sw_sync[0]) begin
// Movimiento por frame (60 Hz)
for (i = 0; i < N; i = i + 1) begin
// Suma velocidades
spr_x[i] <= spr_x[i] + vx[i];
spr_y[i] <= spr_y[i] + vy[i];
// Rebote en límites visibles: [0..799]x[0..599] teniendo en cuenta el tamaño del sprite
if (spr_x[i] <= 0) begin
spr_x[i] <= 0;
vx[i] <= -vx[i];
end else if (spr_x[i] + SW >= 800) begin
spr_x[i] <= 800 - SW;
vx[i] <= -vx[i];
end
if (spr_y[i] <= 0) begin
spr_y[i] <= 0;
vy[i] <= -vy[i];
end else if (spr_y[i] + SH >= 600) begin
spr_y[i] <= 600 - SH;
vy[i] <= -vy[i];
end
end
end
end
end
// Motor de sprites
wire [11:0] rgb_pix;
sprite_engine #(
.N_SPRITES(N),
.SPR_W(SW),
.SPR_H(SH)
) u_spr (
.clk_pix (clk_pix),
.rst (rst_sync),
.x (x),
.y (y),
.de (de),
.ctrl (ctrl_sw_sync),
.spr_x (spr_x),
.spr_y (spr_y),
.spr_en (spr_en),
.rgb (rgb_pix)
);
// Salidas VGA
// VGA HS/VS: positivos para 800x600@60
assign vga_hs = hsync;
assign vga_vs = vsync;
// RGB444: limitar a 4 bits por canal
assign vga_r = rgb_pix[11:8];
assign vga_g = rgb_pix[7:4];
assign vga_b = rgb_pix[3:0];
endmodule
Explicación breve de partes clave:
– clkgen_100to40: usamos MMCME2_BASE (7‑series) con M=8, D=1, O=20 para obtener 40 MHz a partir de 100 MHz (VCO=800 MHz, válido). Evitamos depender de IP “Clocking Wizard” y así el flujo CLI es más directo.
– vga_timing_800x600: genera contadores horizontales y verticales con los timings VESA de 800×600@60; HSYNC y VSYNC con polaridad positiva. de (data enable) es 1 solo en el área activa (0..799, 0..599).
– sprite_engine: compone N sprites de 32×32 con 4bpp usando formas procedurales (círculo, anillo, rayas…). Índice 0 es transparente. Se usa una paleta RGB444 de 16 colores. Se suma una pequeña variación con switches (XOR sobre el índice).
– top_basys3: integra todo, anima los sprites (rebote en los bordes) y permite “empujar” el sprite 0 con los botones. Usa frame_start para actualizar a 60 Hz.
Compilación/flash/ejecución (Vivado 2023.2 WebPACK, CLI exacta)
Estructura recomendada de carpetas:
– proyecto_root/
– src/ (archivos .v)
– constraints/ (XDC)
– build.tcl (script de Vivado)
– vivado_out/ (directorio de trabajo que generará Vivado)
1) Crea la estructura y coloca los archivos:
– Guardar los .v anteriores en:
– src/clkgen_100to40.v
– src/vga_timing_800x600.v
– src/sprite_engine.v
– src/top_basys3.v
– Copiar constraints/Basys3_Master.xdc (oficial de Digilent) y descomentar las secciones indicadas.
2) Script TCL de construcción (build.tcl):
# build.tcl — Vivado 2023.2 WebPACK — Basys 3 (xc7a35tcpg236-1)
# Proyecto: vga-sprite-engine-800x600
set PROJ_NAME "vga_sprite_basys3"
set PROJ_DIR "./vivado_out"
set SRC_DIR "./src"
set XDC_DIR "./constraints"
file mkdir $PROJ_DIR
create_project $PROJ_NAME $PROJ_DIR -part xc7a35tcpg236-1 -force
# Archivos fuente
add_files -fileset sources_1 [list \
$SRC_DIR/clkgen_100to40.v \
$SRC_DIR/vga_timing_800x600.v \
$SRC_DIR/sprite_engine.v \
$SRC_DIR/top_basys3.v \
]
# Constraints (usar el XDC maestro de Basys 3 con líneas pertinentes descomentadas)
add_files -fileset constrs_1 [list \
$XDC_DIR/Basys3_Master.xdc \
]
# Importa y actualiza orden de compilación
import_files -force
update_compile_order -fileset sources_1
# Síntesis e implementación
launch_runs synth_1 -jobs 8
wait_on_run synth_1
launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1
# Exporta bitstream a ruta conocida
set BITFILE "$PROJ_DIR/${PROJ_NAME}.bit"
write_bitstream -force $BITFILE
# Programación del dispositivo
open_hw
connect_hw_server
open_hw_target
# Usar el primer dispositivo disponible
set dev [lindex [get_hw_devices] 0]
current_hw_device $dev
refresh_hw_device $dev
set_property PROGRAM.FILE $BITFILE $dev
program_hw_devices $dev
close_hw
exit
3) Ejecutar en CLI:
– Windows:
– Abre “Vivado 2023.2 Tcl Shell” o usa PowerShell con el entorno configurado, y ejecuta:
– vivado -mode batch -source build.tcl
– Linux (Ubuntu 22.04.3 LTS):
– /opt/Xilinx/Vivado/2023.2/bin/vivado -mode batch -source build.tcl
4) Resultado esperado del flujo:
– Síntesis e implementación sin errores.
– bitstream generado: ./vivado_out/vga_sprite_basys3.bit
– Programación automática de la Basys 3 al final del script.
– El monitor debe salir de “No signal” y mostrar la señal 800×600@60 Hz con sprites moviéndose.
Validación paso a paso
1) Validación “física” básica:
– El monitor debe reconocer 800×600 a 60 Hz. Muchos monitores lo muestran en el OSD (menú de información) como “800×600@60”.
– Si todo está correcto, verás un fondo tipo cuadrícula/barras con 8 sprites de 32×32 moviéndose y rebotando dentro de la ventana.
2) Validación de timings:
– Frecuencia de línea esperada: 40.000 MHz / 1056 ≈ 37.879 kHz.
– Frecuencia de cuadro: 37.879 kHz / 628 ≈ 60.317 Hz.
– Si tienes un osciloscopio y acceso a HSYNC/VSYNC en el conector VGA, deberías observar pulsos cuadrados positivos con estos períodos:
– HSYNC: periodo ≈ 26.4 μs
– VSYNC: periodo ≈ 16.58 ms
3) Validación funcional (interacción):
– Pulsa BTNC (centro) para resetear: los sprites vuelven a posiciones iniciales.
– sw[0] = 1 pausa la animación (los sprites se congelan).
– Pulsa UP/DOWN/LEFT/RIGHT para “empujar” el sprite 0 en esa dirección (acumula velocidad).
– Cambia sw[3:0] para variar la paleta (XOR ligero de índice de color de sprites).
4) Validación visual:
– Debe verse la imagen estable, sin tearing, sin “roll” vertical.
– Los sprites deben superponerse con prioridad (el de mayor índice tapa al anterior).
– Fuera del área activa debe verse negro (bordes estrictos si el monitor no overscan).
5) Validación de polaridad:
– 800×600@60 requiere HS/VS positivos. Si el OSD del monitor muestra otra resolución (p.ej., 640×480) o no sincroniza, revisa la polaridad (nuestro módulo ya genera +/+).
Troubleshooting (5–8 casos típicos)
1) Pantalla negra o “No signal”
– Causas probables:
– No has descomentado en el XDC las líneas de VGA (R, G, B, HS, VS) o del reloj.
– No hay programación (bitstream no cargado).
– El cable VGA está suelto o el monitor está en otra entrada.
– Solución:
– Revisa constraints/Basys3_Master.xdc y asegúrate de que los puertos HDL coinciden con los nombres del XDC.
– Reprograma con: vivado -mode batch -source build.tcl.
– Verifica que el monitor está en la entrada VGA correcta.
2) Resolución/refresh incorrectos (p.ej., 800×600@72 o 1024×768 detectados)
– Causas probables:
– Pixel clock ≠ 40 MHz (parámetros erróneos del MMCM).
– Timings H/V mal configurados (porches/sync).
– Solución:
– Verifica MMCME2: M=8, D=1, O=20 → 1008/(120)=40 MHz.
– Revisa vga_timing_800x600: H: 800/40/128/88; V: 600/1/4/23; polaridad +/+.
3) Imagen desplazada o inestable
– Causas probables:
– De (data enable) mal calculado o RGB no puesto a 0 fuera del área activa.
– HS/VS invertidos (polarity).
– Solución:
– Confirmar que de = (x<800)&&(y<600) y que fuera de de se pone RGB=0.
– Verifica que se asigna vga_hs=hsync y vga_vs=vsync (no negados).
4) Colores erróneos o saturados
– Causas probables:
– Orden de bits RGB444 invertido (MSB/LSB).
– Paleta con valores fuera de 4 bits por canal.
– Solución:
– Asegura que vga_r = rgb[11:8], vga_g = rgb[7:4], vga_b = rgb[3:0].
– Mantén 12’hXYZ con X,Y,Z en 0..F.
5) Vivado no detecta el dispositivo en JTAG
– Causas probables:
– Falta de drivers del cable Digilent.
– No hay permisos (Linux) para el dispositivo USB.
– El interruptor de encendido de la placa está apagado.
– Solución:
– Reinstala/actualiza drivers con Vivado 2023.2.
– En Linux, agrega una regla udev para Digilent o ejecuta hw_server/vivado con permisos adecuados.
– Verifica alimentación (LED ON).
6) Fallos de timing (impl_1 con violaciones)
– Causas probables:
– Cambios no sincronizados o lógica muy compleja añadida por el usuario.
– Solución:
– Este diseño a 40 MHz es holgado; si añadiste más sprites o lógicas, añade registros de pipeline.
– Mantén los caminos combinacionales cortos en el motor de sprites.
7) Artefactos en bordes de sprites (parpadeo)
– Causa:
– Cálculo de coordenadas locales en la misma etapa que composición con muchas condiciones.
– Solución:
– Registra x,y y/o resultados intermedios (1 pipeline stage) si añades mucha lógica.
8) El monitor muestra “fuera de rango”
– Causa:
– Timings no estándar o polaridades incorrectas.
– Solución:
– Asegúrate de usar exactamente los valores VESA dados y HS/VS positivos.
Mejoras/variantes
- Escalado y tamaños variables:
- Permitir sprites de 16×16/32×32/64×64 con un campo “tamaño” en atributos y escalado nearest-neighbor.
- Memoria de sprites en BRAM:
- Reemplazar el generador procedural por ROMs inferidas ($readmemh) para cargar gráficos reales (p.ej., 4bpp con paleta).
- Z‑buffer o prioridad por atributo:
- Añadir un campo de prioridad por sprite que defina el orden de composición dinámicamente.
- Tilemap de fondo:
- Generar un fondo con tiles 8×8 o 16×16 usando BRAM y una tabla de patrones (tipo retro GPU). La Basys 3 tiene BRAM suficiente para mapas pequeños.
- Doble buffer de línea:
- Si aumentas la complejidad de composición, agrega un linebuffer en BRAM (800×12b ≈ 9.6 kbit) y compón por fases (fondos → sprites).
- Animación avanzada:
- Tabla de atributos (x,y, frame, enable, prioridad, paleta) actualizada por un microcontrolador soft (MicroBlaze bare‑metal) vía AXI‑Lite (WebPACK lo soporta).
- Efectos:
- Blending alfa simplificado (1 bit de alfa: semitransparencia), outline, sombras proyectadas, paletas cíclicas.
Checklist de verificación
- [ ] Instalé Xilinx Vivado 2023.2 (WebPACK) y hw_server 2023.2 en mi SO (Windows 11 22H2 o Ubuntu 22.04.3 LTS).
- [ ] Conecté la Basys 3 por micro‑USB y el cable VGA al monitor; coloqué JP1 en “USB” y encendí la placa.
- [ ] Creé la estructura de carpetas (src/, constraints/) y coloqué los .v y el XDC maestro de Basys 3.
- [ ] Descomenté en el XDC las secciones de CLK100MHZ, SW[15:0], BTNs y señales VGA (R,G,B, HS, VS).
- [ ] Ejecuté: vivado -mode batch -source build.tcl y finalizó sin errores.
- [ ] El monitor muestra 800×600@60 y veo el fondo con sprites moviéndose.
- [ ] BTNC resetea; sw[0] pausa; UP/DOWN/LEFT/RIGHT empujan el sprite 0.
- [ ] Visualmente no hay tearing; los colores parecen correctos (RGB444).
- [ ] Guardé el bitstream (vivado_out/vga_sprite_basys3.bit) para reuso.
Notas finales de coherencia:
– Modelo exacto de dispositivo: Digilent Basys 3 (Xilinx Artix‑7 xc7a35tcpg236‑1).
– Toolchain: Vivado 2023.2 WebPACK, con flujo 100% CLI (TCL). No se usan IPs; el MMCM se instancia con MMCME2_BASE.
– Objetivo del proyecto: vga-sprite-engine-800×600, ejecutado a 40 MHz con timings VESA y polaridad de sincronismos positiva, coherente con el hardware de la Basys 3.
– Materiales, conexión, código y comandos reproducibles y alineados con el modelo y la toolchain indicada.
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.


