Lectura de imágenes BMP en testbenches VHDL con TEXTIO
Al trabajar con testbenches VHDL, la forma más sencilla de importar una imagen es usar el formato de gráficos raster BMP. Los archivos BMP son nativamente compatibles con Windows y exponen datos de píxeles sin compresión, lo que los hace ideales para simulación.
Este artículo explica cómo leer un archivo BMP binario, almacenar sus datos de píxeles en memoria dinámica, alimentarlos a un DUT VHDL y escribir la imagen procesada de vuelta a un nuevo archivo BMP. El DUT de ejemplo realiza una conversión a escala de grises, pero la técnica se aplica a cualquier módulo de procesamiento píxel a píxel.
Parte de una serie TEXTIO:
- Inicializar RAM desde archivo usando TEXTIO
- Lectura de archivo de estímulo en testbench usando TEXTIO
Por qué BMP es el formato óptimo para VHDL
JPEG y PNG son ubicuos en la web, pero ambos emplean compresión. JPEG es con pérdidas, PNG es sin pérdidas; ambos añaden una capa de complejidad cuando un testbench debe acceder a datos de píxeles directamente. BMP, por otro lado, almacena datos de imagen como gráficos raster sin compresión, así que cada píxel está representado por bytes RGB crudos. Esto elimina la necesidad de código de descompresión en VHDL y garantiza tiempo de simulación determinista.
Editores de imagen comunes como Photoshop o GIMP cargan imágenes en memoria como datos raster internamente. Para VHDL, convertir la imagen fuente a BMP manualmente (o mediante script) es el enfoque más directo.
Preparación del archivo BMP
Use Paint de Windows para asegurar un diseño de archivo consistente:
- Abra la imagen.
- Seleccione Archivo > Guardar como.
- Elija Bitmap de 24 bits (*.bmp; *.dib) y guarde.
Con este método, la cabecera del archivo será siempre la variante BITMAPINFOHEADER de 54 bytes con formato de píxel RGB24. La siguiente tabla lista los campos de cabecera que leemos.
| Offset (Dec) | Size (B) | Expected (Hex) | Description |
|---|---|---|---|
| 0 | 2 | 42 4D | Campo ID |
| 10 | 4 | 54 (36 00 00 00) | Offset de array de píxeles |
| 14 | 4 | 40 (28 00 00 00) | Tamaño de cabecera |
| 18 | 4 | Read value | Ancho de imagen en píxeles |
| 22 | 4 | Read value | Alto de imagen en píxeles |
| 26 | 1 | 01 | Número de planos de color |
| 28 | 1 | 18 | Bits por píxel |
Solo los campos sombreados en verde son esenciales; el resto sirven como comprobaciones de cordura.
El caso de prueba
A continuación se muestra un DUT mínimo de escala de grises que acepta entrada RGB de 24 bits y emite el valor de luminancia correspondiente codificado de nuevo en RGB. El módulo es puramente combinacional, por lo que no se requiere reloj.
entity grayscale is port ( r_in : in std_logic_vector(7 downto 0); g_in : in std_logic_vector(7 downto 0); b_in : in std_logic_vector(7 downto 0); r_out : out std_logic_vector(7 downto 0); g_out : out std_logic_vector(7 downto 0); b_out : out std_logic_vector(7 downto 0) ); end grayscale;
Para completitud, el código fuente completo del proyecto (incluyendo un BMP de Boeing 747 de 1000×1000 píxeles) está disponible para descarga proporcionando una dirección de correo en el formulario vinculado.
Importación de TEXTIO
Al principio de su archivo VHDL añada:
use std.textio.all; use std.env.finish;
Estas declaraciones requieren VHDL‑2008 o posterior.
Tipos personalizados para almacenamiento de imagen
Defina los siguientes tipos en la región declarativa de su testbench:
type header_type is array (0 to 53) of character; type pixel_type is record red : std_logic_vector(7 downto 0); green : std_logic_vector(7 downto 0); blue : std_logic_vector(7 downto 0); end record; type row_type is array (integer range <>) of pixel_type; type row_pointer is access row_type; type image_type is array (integer range <>) of row_pointer; type image_pointer is access image_type;
Estas estructuras de datos dinámicas permiten al testbench acomodar imágenes de cualquier tamaño sin límites de array estático.
Instanciación del DUT
signal r_in, g_in, b_in : std_logic_vector(7 downto 0); signal r_out, g_out, b_out : std_logic_vector(7 downto 0); begin DUT : entity work.grayscale port map ( r_in => r_in, g_in => g_in, b_in => b_in, r_out => r_out, g_out => g_out, b_out => b_out );
Lectura de la cabecera BMP
El proceso comienza cargando la cabecera de 54 bytes en una variable header y validando campos clave con sentencias assert. Esta guarda previene el uso accidental de un formato de archivo no soportado.
for i in header_type'range loop read(bmp_file, header(i)); end loop; -- Comprobaciones básicas de cordura assert header(0) = 'B' and header(1) = 'M' report "First two bytes are not ""BM"". This is not a BMP file" severity failure; assert character'pos(header(10)) = 54 and character'pos(header(11)) = 0 and character'pos(header(12)) = 0 and character'pos(header(13)) = 0 report "Pixel array offset is not 54 bytes" severity failure; assert character'pos(header(14)) = 40 and character'pos(header(15)) = 0 and character'pos(header(16)) = 0 and character'pos(header(17)) = 0 report "DIB header size is not 40 bytes, is this a Windows BMP?" severity failure; assert character'pos(header(26)) = 1 and character'pos(header(27)) = 0 report "Color planes is not 1" severity failure; assert character'pos(header(28)) = 24 and character'pos(header(29)) = 0 report "Bits per pixel is not 24" severity failure;
Tras la validación, se extraen el ancho y alto de la imagen:
image_width := character'pos(header(18)) + character'pos(header(19)) * 2**8 + character'pos(header(20)) * 2**16 + character'pos(header(21)) * 2**24; image_height := character'pos(header(22)) + character'pos(header(23)) * 2**8 + character'pos(header(24)) * 2**16 + character'pos(header(25)) * 2**24; report "image_width: " & integer'image(image_width) & ", image_height: " & integer'image(image_height);
Lectura de datos de píxeles
Cada fila BMP se rellena a múltiplo de cuatro bytes. Calcule el relleno por fila y asigne almacenamiento dinámico para la imagen.
padding := (4 - image_width*3 mod 4) mod 4; image := new image_type(0 to image_height - 1); for row_i in 0 to image_height - 1 loop row := new row_type(0 to image_width - 1); for col_i in 0 to image_width - 1 loop read(bmp_file, char); row(col_i).blue := std_logic_vector(to_unsigned(character'pos(char), 8)); read(bmp_file, char); row(col_i).green := std_logic_vector(to_unsigned(character'pos(char), 8)); read(bmp_file, char); row(col_i).red := std_logic_vector(to_unsigned(character'pos(char), 8)); end loop; for i in 1 to padding loop read(bmp_file, char); end loop; -- descartar relleno image(row_i) := row; end loop;
Prueba del DUT
Alimente cada píxel al DUT, espere un pequeño ciclo delta, y capture la salida en escala de grises.
for row_i in 0 to image_height - 1 loop row := image(row_i); for col_i in 0 to image_width - 1 loop r_in <= row(col_i).red; g_in <= row(col_i).green; b_in <= row(col_i).blue; wait for 10 ns; row(col_i).red := r_out; row(col_i).green := g_out; row(col_i).blue := b_out; end loop; end loop;
Escritura del BMP de salida
Primero, escriba la cabecera preservada. Luego serialice los datos de píxeles fila por fila, añadiendo relleno donde sea necesario. Finalmente, limpie memoria dinámica y cierre los archivos.
for i in header_type'range loop write(out_file, header(i)); end loop; for row_i in 0 to image_height - 1 loop row := image(row_i); for col_i in 0 to image_width - 1 loop write(out_file, character'val(to_integer(unsigned(row(col_i).blue)))); write(out_file, character'val(to_integer(unsigned(row(col_i).green)))); write(out_file, character'val(to_integer(unsigned(row(col_i).red)))); end loop; deallocate(row); for i in 1 to padding loop write(out_file, character'val(0)); end loop; end loop; deallocate(image); file_close(bmp_file); file_close(out_file); report "Simulation done. Check ""out.bmp"" image."; finish; end process;
Imagen resultante
El archivo out.bmp generado muestra la versión en escala de grises de la imagen original del Boeing 747. Una vista previa JPEG se muestra en el artículo; el archivo BMP completo está disponible vía el formulario de descarga.
Consideraciones adicionales
- Para procesamiento de imagen de alto rendimiento, considere codificación YUV donde la luminancia (Y) se separa de la crominancia (U/V). La conversión entre RGB y YUV es directa y a menudo más amigable para hardware.
- La conversión CMYK es más compleja porque no existe mapeo píxel a píxel uno a uno; típicamente requiere tabla de búsqueda o algoritmo más elaborado.
- Al trabajar con codificaciones personalizadas, puede diseñar un formato de archivo ligero (p.ej.,
.yuvo.cmyk) que omita la cabecera BMP por completo, simplificando el parseo. - Automatizar el paso de conversión a BMP (p.ej., mediante el comando
convertde ImageMagick) asegura vectores de prueba consistentes entre ejecuciones de simulación.
VHDL
- Holografía Desmitificada: Desde la Invención Nobel de Gabor hasta el Almacenamiento de Datos del Futuro
- Centro de Investigación Langley de la NASA presenta cámara plenóptica avanzada para imagen 3D en color
- Uso de firmas digitales para verificar integridad de archivos binarios en Linux
- Dominando las Sentencias Using en C#: Importaciones, Alias y Directivas Estáticas
- Manejo de archivos en C: Dominando E/S con fopen, fprintf, fread y fseek
- Entendiendo los Archivos de Cabecera en C: Tipos, Uso y Buenas Prácticas
- Inicialización de RAM de bloques FPGA desde archivos de texto usando la biblioteca TEXTIO de VHDL
- Dominando la E/S de Archivos en Verilog: Abrir, Leer, Escribir y Cerrar
- Python JSON: Codificación, decodificación y manejo de archivos – Guía práctica
- Dominando las operaciones de archivos en Java con java.io: creación, lectura, escritura y eliminación
- Lectura de archivos en Java con BufferedReader – Guía práctica con ejemplos