Lendo Imagens BMP Bitmap em Testbenches VHDL com TEXTIO
Ao trabalhar com testbenches VHDL, a maneira mais simples de importar uma imagem é usar o formato de gráficos raster BMP. Arquivos BMP são nativamente suportados pelo Windows e expõem dados de pixel brutos sem compressão, tornando-os ideais para simulação.
Este artigo explica como ler um arquivo BMP binário, armazenar seus dados de pixel em memória dinâmica, alimentá-lo a um DUT VHDL e gravar a imagem processada de volta em um novo arquivo BMP. O DUT de exemplo realiza conversão para escala de cinza, mas a técnica se aplica a qualquer módulo de processamento pixel a pixel.
Parte de uma série TEXTIO:
- Inicializar RAM a partir de arquivo usando TEXTIO
- Leitura de arquivo de estímulo em testbench usando TEXTIO
Por que BMP é o Formato Ideal para VHDL
JPEG e PNG são ubíquos na web, mas ambos empregam compressão. JPEG é lossy, PNG é lossless; ambos adicionam uma camada de complexidade quando um testbench deve acessar dados de pixel diretamente. BMP, por outro lado, armazena dados de imagem como gráficos raster não comprimidos, então cada pixel é representado por bytes RGB brutos. Isso elimina a necessidade de código de descompressão em VHDL e garante tempo de simulação determinístico.
Editores de imagem comuns como Photoshop ou GIMP carregam imagens na memória como dados raster internamente. Para VHDL, converter a imagem fonte para BMP manualmente (ou via script) é a abordagem mais direta.
Preparando o Arquivo BMP
Use o Windows Paint para garantir um layout de arquivo consistente:
- Abra a imagem.
- Selecione File > Save As.
- Escolha 24-bit Bitmap (*.bmp; *.dib) e salve.
Com este método, o cabeçalho do arquivo será sempre a variante de 54 bytes BITMAPINFOHEADER com formato de pixel RGB24. A tabela a seguir lista os campos do cabeçalho que lemos.
| Offset (Dec) | Size (B) | Expected (Hex) | Description |
|---|---|---|---|
| 0 | 2 | 42 4D | ID field |
| 10 | 4 | 54 (36 00 00 00) | Pixel array offset |
| 14 | 4 | 40 (28 00 00 00) | Header size |
| 18 | 4 | Read value | Image width in pixels |
| 22 | 4 | Read value | Image height in pixels |
| 26 | 1 | 01 | Number of color planes |
| 28 | 1 | 18 | Number of bits per pixel |
Apenas os campos sombreados em verde são essenciais; o resto serve como verificação de sanidade.
O Caso de Teste
Abaixo está um DUT mínimo de escala de cinza que aceita entrada RGB de 24 bits e outputs o valor de luminância correspondente codificado de volta em RGB. O módulo é puramente combinacional, então nenhum clock é necessário.
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 completude, o código fonte completo do projeto (incluindo um BMP Boeing 747 de 1000x1000 pixels) está disponível para download fornecendo um endereço de e-mail no formulário linkado.
Importando TEXTIO
No topo do seu arquivo VHDL adicione:
use std.textio.all; use std.env.finish;
Estas declarações requerem VHDL-2008 ou mais recente.
Tipos Personalizados para Armazenamento de Imagem
Defina os seguintes tipos na região declarativa do seu 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 estruturas de dados dinâmicas permitem que o testbench acomode imagens de qualquer tamanho sem limites de array estático.
Instanciando o 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 );
Lendo o Cabeçalho BMP
O processo começa carregando o cabeçalho de 54 bytes em uma variável header e validando campos-chave com declarações assert. Esta guarda previne uso acidental de formato de arquivo não suportado.
for i in header_type'range loop read(bmp_file, header(i)); end loop; -- Basic sanity checks 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;
Após validação, a largura e altura da imagem são extraídas:
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);
Lendo Dados de Pixel
Cada linha BMP é preenchida para múltiplo de quatro bytes. Calcule o padding por linha e aloque armazenamento dinâmico para a imagem.
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; -- discard padding image(row_i) := row; end loop;
Testando o DUT
Alimente cada pixel no DUT, espere um pequeno ciclo delta, e capture a saída em escala de cinza.
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;
Gravando o BMP de Saída
Primeiro, grave o cabeçalho preservado. Depois, serializa os dados de pixel linha por linha, adicionando padding onde necessário. Finalmente, limpe memória dinâmica e feche os arquivos.
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;
Imagem Resultante
O arquivo out.bmp gerado mostra a versão em escala de cinza da imagem original do Boeing 747. Uma prévia JPEG é exibida no artigo; o arquivo BMP completo está disponível via formulário de download.
Considerações Adicionais
- Para processamento de imagem de alta vazão, considere codificação YUV onde luminância (Y) é separada de crominância (U/V). Converter entre RGB e YUV é direto e frequentemente mais amigável a hardware.
- Conversão CMYK é mais complexa porque mapeamento pixel a pixel um-para-um não existe; tipicamente requer lookup table ou algoritmo mais elaborado.
- Ao trabalhar com codificações customizadas, você pode projetar um formato de arquivo leve (ex:
.yuvou.cmyk) que omite o cabeçalho BMP inteiramente, simplificando parsing. - Automatizar a etapa de conversão BMP (ex: via comando
convertdo ImageMagick) garante vetores de teste consistentes entre execuções de simulação.
VHDL
- Holografia Desmistificada: Da Invenção Nobel de Gabor ao Armazenamento de Dados do Futuro
- Dominando Declarações Using em C#: Imports, Aliases e Diretivas Estáticas
- Manipulação de Arquivos em C: Dominando I/O com fopen, fprintf, fread e fseek
- Dominando Operações de Arquivo em Java com java.io – Criação, Leitura, Escrita e Exclusão
- Inicializando Block RAM de FPGA a partir de Arquivos de Texto Usando a Biblioteca TEXTIO do VHDL
- Lendo Arquivos em Java com BufferedReader – Guia Prático com Exemplos
- Python JSON: Codificação, Decodificação e Manipulação de Arquivos – Guia Prático
- Dominando I/O de Arquivos em Verilog: Abrir, Ler, Escrever e Fechar
- Entendendo Arquivos de Cabeçalho em C: Tipos, Uso e Boas Práticas
- Centro de Pesquisa Langley da NASA Revela Câmera Plenóptica Avançada para Imagem 3D Colorida