Industrial manufacturing
Industrial Internet of Things | Industrial materials | Equipment Maintenance and Repair | Industrial programming |
home  MfgRobots >> Industrial manufacturing >  >> Industrial programming >> VHDL

Reading BMP Bitmap Images in VHDL Testbenches with TEXTIO

When working with VHDL testbenches, the simplest way to import an image is to use the BMP raster graphics format. BMP files are natively supported by Windows, and they expose raw pixel data without compression, making them ideal for simulation.

This article explains how to read a binary BMP file, store its pixel data in dynamic memory, feed it to a VHDL DUT, and write the processed image back to a new BMP file. The example DUT performs a grayscale conversion, but the technique applies to any pixel‑wise processing module.

Part of a TEXTIO series:

Why BMP is the Optimal Format for VHDL

JPEG and PNG are ubiquitous on the web, but both employ compression. JPEG is lossy, PNG is lossless; both add a layer of complexity when a testbench must access pixel data directly. BMP, on the other hand, stores image data as uncompressed raster graphics, so each pixel is represented by raw RGB bytes. This eliminates the need for decompression code in VHDL and guarantees deterministic simulation time.

Common image editors such as Photoshop or GIMP load images into memory as raster data internally. For VHDL, converting the source image to BMP manually (or via a script) is the most straightforward approach.

Preparing the BMP File

Use Windows Paint to ensure a consistent file layout:

  1. Open the image.
  2. Select File > Save As.
  3. Choose 24‑bit Bitmap (*.bmp; *.dib) and save.

With this method the file header will always be the 54‑byte BITMAPINFOHEADER variant with an RGB24 pixel format. The following table lists the header fields we read.

Offset (Dec)Size (B)Expected (Hex)Description
0242 4DID field
10454 (36 00 00 00)Pixel array offset
14440 (28 00 00 00)Header size
184Read valueImage width in pixels
224Read valueImage height in pixels
26101Number of color planes
28118Number of bits per pixel

Only the green‑shaded fields are essential; the rest serve as sanity checks.

The Test Case

Below is a minimal grayscale DUT that accepts 24‑bit RGB input and outputs the corresponding luminance value encoded back into RGB. The module is purely combinational, so no clock is required.

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;

For completeness, the full project source (including a 1000×1000 pixel Boeing 747 BMP) is available for download by providing an email address on the linked form.

Importing TEXTIO

At the top of your VHDL file add:

use std.textio.all;
use std.env.finish;

These declarations require VHDL‑2008 or newer.

Custom Types for Image Storage

Define the following types in the declarative region of your 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;

These dynamic data structures allow the testbench to accommodate images of any size without static array limits.

Instantiating the 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
    );

Reading the BMP Header

The process starts by loading the 54‑byte header into a header variable and validating key fields with assert statements. This guard prevents accidental use of an unsupported file format.

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;

After validation, the image width and height are extracted:

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);

Reading Pixel Data

Each BMP row is padded to a multiple of four bytes. Compute the padding per row and allocate dynamic storage for the image.

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;

Testing the DUT

Feed each pixel into the DUT, wait a small delta cycle, and capture the grayscale output.

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;

Writing the Output BMP

First, write the preserved header. Then serialize the pixel data row by row, adding padding where necessary. Finally, clean up dynamic memory and close the files.

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;

Resulting Image

The generated out.bmp file shows the grayscale version of the original Boeing 747 image. A JPEG preview is displayed in the article; the full BMP file is available via the download form.

Further Considerations

VHDL

  1. Driving VHDL Testbenches from External Stimulus Files with TEXTIO
  2. Initializing FPGA Block RAM from Text Files Using VHDL’s TEXTIO Library
  3. C++ File Handling: Mastering Open, Read, Write, and Close Operations
  4. File Operations in C# – A Practical Guide
  5. Reading Files in Java with BufferedReader – A Practical Guide with Examples
  6. How to Rename Files and Directories in Python with os.rename() – Step-by-Step Guide
  7. How to Read and Write CSV Files in Python: A Comprehensive Guide
  8. Python JSON: Encoding, Decoding, and File Handling – A Practical Guide
  9. Seamless PLC‑to‑Cloud Integration: Harnessing IoT for Real‑Time Data Retrieval
  10. Robust UAV Detection & Tracking via AI-Driven Computer Vision