En el siguiente tutorial veremos cómo hacer funcionar un monitor VGA en la BASYS 3. Primero cargaremos un módulo de prueba para verificar que funciona correctamente y luego veremos un ejemplo de cómo utilizar el monitor VGA para mostrar información como el valor de un contador y los datos que circulan por un bus de 32 bits.
Crea un proyecto nuevo en la suite Vivado, descarga el módulo vga_ctrl.v (disponible al final de la página) y programa la BASYS 3.
Este módulo está basado en el original de Digilent descrito en VHDL y forma parte de la demo que se ejecuta en la BASYS 3 cuando se enciende. Consiste en un controlador VGA completo para una pantalla de 640×480 @ 60 Hz, pensado para la Basys 3. Mas info aquí.
En concreto, este módulo se encarga de:
1. Generar las señales VGA necesarias
Produce las señales de sincronismo horizontal VGA_HS_O y vertical VGA_VS_O y las salidas de vídeo en formato RGB444 → 4 bits por color VGA_RED_O, VGA_GREEN_O y VGA_BLUE_O. Estas señales son las que necesita un monitor VGA para dibujar cada fotograma.
2. Dividir el reloj de 100 MHz a 25 MHz
La Basys 3 usa un reloj de 100 MHz, pero VGA 640×480 necesita un pixel-clock de 25.175 MHz. Un divisor de frecuencia genera la señal de reloj de 25 MHz.
3. Contadores horizontales y verticales
El módulo incluye dos contadores uno horizontal para los píxeles y otro vertical para las líneas. Con estos contadores se generan las zonas visibles y los pulsos de sincronía.
4. Generar la ventana visible
Con la siguiente declaración, el módulo calcula si el píxel actual está dentro de la región visible y determina cuándo enviar color y cuándo apagar la pantalla.
if (h_cnt < 640 && v_cnt < 480)
display_enable = 1;
else
display_enable = 0;
5. Generar color del píxel
La parte combinacional del módulo produce la imagen de prueba: cuadrantes de colores, barras, y triángulos moviéndose de izquierda a derecha.
Si las características de tu monitor son las adecuadas, verás una imagen en movimiento como la siguiente.
En el siguiente ejemplo veremos cómo mostrar dos líneas de datos en el monitor VGA. La primera línea es un contador de 32 bits que usaremos a modo de heartbeat para verificar que el programa está vivo y la segunda línea es un bus de datos de 32 bits cuya información se muestra en formato hexadecimal.
¿Cómo funciona? veamos:
El Controlador VGA genera las señales de sincronismo VGA_HS y VGA_VS, las coordenadas actuales del píxel actual: px, py y la señal active que indica si el píxel está en zona visible.
vga_ctrl vga(
.clk(clk25),
.hs(VGA_HS),
.vs(VGA_VS),
.px(px),
.py(py),
.active(active)
);
El contenido del bus y del contador se divide en nibbles (grupos de 4 bits). Esto permite mostrar cada nibble como un dígito hexadecimal en pantalla.
wire [3:0] C0 = counter[3:0];
wire [3:0] C1 = counter[7:4];
wire [3:0] C2 = counter[11:8];
wire [3:0] C3 = counter[15:12];
wire [3:0] C4 = counter[19:16];
wire [3:0] C5 = counter[23:20];
wire [3:0] C6 = counter[27:24];
wire [3:0] C7 = counter[31:28];
wire [3:0] B0 = data_bus_in[3:0];
wire [3:0] B1 = data_bus_in[7:4];
wire [3:0] B2 = data_bus_in[11:8];
wire [3:0] B3 = data_bus_in[15:12];
wire [3:0] B4 = data_bus_in[19:16];
wire [3:0] B5 = data_bus_in[23:20];
wire [3:0] B6 = data_bus_in[27:24];
wire [3:0] B7 = data_bus_in[31:28];
Las dos instancias del módulo text_renderer.v generan las líneas de texto CNT:12345678 y BUS:DEADBEEF que luego se muestran en el monitor VGA
// CNT: + contador
text_renderer #(.SCALE(3), .TEXT_LEN(12), .X0(50), .Y0(40)) tr_counter (
.px(px), .py(py), .active(active),
.hex0(4'hC), .hex1(5'h11), .hex2(5'h19), .hex3(5'h1F), // CNT:
.hex4(C7), .hex5(C6), .hex6(C5), .hex7(C4),
.hex8(C3), .hex9(C2), .hex10(C1), .hex11(C0),
.pixel_on(pix_counter)
);
// BUS: + datos
text_renderer #(.SCALE(3), .TEXT_LEN(12), .X0(50), .Y0(120)) tr_bus (
.px(px), .py(py), .active(active),
.hex0(4'hB), .hex1(5'h17), .hex2(5'h12), .hex3(5'h1F), // B U S :
.hex4(B7), .hex5(B6), .hex6(B5), .hex7(B4),
.hex8(B3), .hex9(B2), .hex10(B1), .hex11(B0),
.pixel_on(pix_bus)
);
Importante no olvidar declara las señales pix_counter y wire pix_bus para hacer OR al final del módulo:
wire pixel = pix_counter | pix_bus;
El módulo text_renderer.v es un renderizador de texto para VGA, diseñado para mostrar una línea de caracteres en pantalla usando una fuente 8×16 ampliada por un factor configurable. Convierte los 12 valores hexadecimales (o códigos extendidos) en píxeles que se encienden o apagan según la fuente incluida en font8x16_extended.v.
Las letras que se muestran en el monitor se codifican en el módulo de fuentes font8x16_extended.v. Su función principal es tomar un código de carácter (nibble) y un número de fila, y generar los datos de píxeles (bits) correspondientes a esa fila del carácter seleccionado. Este módulo actúa esencialmente como una ROM)para gráficos de caracteres.
Por simplicidad se ha reducido el número de caracteres codificado en la ROM a los números 0-9 y algunas letras mayúsculas. Puedes añadir el resto de caracteres ASCII si lo necesitas en tu proyecto.
Genera el bitstream y programa la FPGA.
Los datos del BUS los simulamos son los switches 0-15 y las entradas PMOD de la BASYS 3. Si todo va bien verás dos líneas de datos en tu monitor VGA.
Si quieres mostrar más líneas de datos, añade un nueva entrada en el módulo top y crea nuevas instancias del módulo text_renderer.v
Las letras que se muestran en el monitor se codifican en el módulo de fuentes font8x16_extended.v. Puedes añadir nuevos caracteres personalizados según las necesidades de cada proyecto.
Eso es todo por el momento, espero que os haya gustado. ¡Hasta pronto!
Descarga las fuentes de los ejemplos 1 y 2 aquí abajo.