You dont have javascript enabled! Please enable it!

Caso práctico: Motor sprites VGA 800×600 en Digilent Basys 3

Caso práctico: Motor sprites VGA 800x600 en Digilent Basys 3 — hero

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

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: ¿Cuál es el sistema operativo soportado para el uso de la herramienta Vivado?




Pregunta 2: ¿Qué herramienta de diseño se debe utilizar según el artículo?




Pregunta 3: ¿Cuál es la cantidad mínima de RAM recomendada para implementaciones con Artix‑7?




Pregunta 4: ¿Qué tipo de dispositivo se está utilizando en el artículo?




Pregunta 5: ¿Qué frecuencia se desea obtener desde la primitiva MMCME2_BASE?




Pregunta 6: ¿Qué tipo de conexión se requiere para la alimentación y programación de la Basys 3?




Pregunta 7: ¿Cuál es la resolución mínima aceptada por el monitor VGA mencionado?




Pregunta 8: ¿Qué tipo de CPU se requiere para el uso de Vivado?




Pregunta 9: ¿Qué se debe hacer con el jumper de alimentación JP1 antes de encender la placa?




Pregunta 10: ¿Cuánto espacio libre en disco se recomienda para la instalación y proyectos?




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