Reading image files with VHDL part 1

Please note that this page talks rubbish – here’s why, and here’s a better version.

If we’re going to do some image processing in VHDL, we’re going to need to be able to read images in to our simulations. VHDL’s file handling is pretty poor to say the least, so we’ll keep things simple by only handling a very simple file format – namely Portable Greymap (PGM). And for even more simplicity we’ll handle on one variant with 8-bit pixels (P2 format with Maxval<256).

Data storage

Please note that this page talks rubbish – here’s why, and here’s a better version.

VHDL’s handling of 2-D arrays is also pretty dismal – one of the dimensions has to be known at compile-time, which is not great for reading in arbitrary images. To get around this, we’ll store the pixels in a one-dimensional vector in a record along with the image height and width. This will allow us to access any pixel with an (x,y) coordinate by calculating the index:

index = y*width+x

Let’s create a package called pgm to keep all this together:

package pgm is

subtype pixel is integer range 0 to 255;
type pixel_vector is array (integer range &lt;&gt;) of pixel;
type pixel_vector_ptr is access pixel_vector;
type image is record  -- storage for all the relevant info
    width, height : positive;
    pixels        : pixel_vector_ptr;
end record image;
subtype coordinate is natural;


Some points:

  • We’ve got a subtype for the raw pixel type – this enables us to change a single place if we decide to extend to (say) 16-bit pixels.
  • The record is storing an ACCESS type, like a pointer. This allows us to allocate the memory for the pixel_vector on-the-fly when the image is first read
  • The downside of this is that the image type cannot be passed to functions, so we must use procedures with inout parameters for passing images in


The public API for playing with the images will be simple:

  • a function to read an image from a file into an image record
  • a convenience function to read a particular pixel using (x,y) coordinates
  • a convenience function to set a particular pixel
  • a function to write an image record out to a file

function pgm_read (
filename : string)
return image;
procedure pgm_write (
filename : in string;
i : inout image);

procedure get_pixel (
    i    : inout image;
    x, y : in    coordinate;
    val  : out   pixel);
procedure set_pixel (
    i    : inout image;
    x, y : in    coordinate;
    val  : in    pixel);

end package pgm;


For the input side, a simple testbench is all that is required, along with a carefully crafted PGM file for which we “know the right answers”. Following that, we can change one or more pixels with the set_pixel() interface, check that it has been done correctly, and write the new image out. Checking that final image will have to be done by hand. We could “round-trip” the image back in through our image reading function, but this may mask any errors we’ve made in the reading and writing functions which happen to cancel each other out!

entity tb_pgm is
end entity tb_pgm;

use work.libv.all;
use work.pgm.all;
architecture test of tb_pgm is
begin — architecture test
test1: process is
variable i : image;
constant testdata : pixel_vector := (
begin — process test1
i := pgm_read("test.pgm");
assert_equal("PGM Width", i.width, 8);
assert_equal("PGM Height", i.width, 4);
assert_equal("PGM data", integer_vector(i.pixels.all), integer_vector(testdata));
end process test1;

end architecture test;

The constant testdata is what we will “draw” into our image with a paint package in order to create the input image for testing.

Aside – libv

The assert_equal functions are part of my library of useful things (maybe this could be a start towards VHDL’s jQuery :) libv. Why not just use assert as is? Well, it’s useful to report all assert results in the same fashion, particularly to include the values that you expected and what you tested, along with the difference between them – often this is useful debugging information.

procedure assert_equal (
prefix : string;
got, expected : integer;
level : severity_level := error) is
begin — procedure assert_equal
assert got = expected
report prefix & " wrong. Got " & integer’image(got) & " expected " & integer’image(expected) & "(difference=" & integer’image(got-expected) &")"
severity level;
end procedure assert_equal;

In addition, the same API can be used for comparing more complex types (like the integer_vector type which is also part of libv):

procedure assert_equal (
prefix : string;
got, expected : integer_vector;
level : severity_level := error) is
variable g,e : integer;
begin — procedure assert_equal
assert got’length = expected’length
report prefix & " length wrong. Got " & integer’image(got’length)
& " expected " & integer’image(expected’length)
& "(difference=" & integer’image(got’length-expected’length) &")"
severity level;
for i in 0 to got’length-1 loop
g := got(got’low+i);
e := expected(expected’low+i);
assert g = e
report prefix
& " got(" & integer’image(got’low+i) & ") = " & integer’image(g) & CR
& " expected(" & integer’image(expected’low+i) & ") = " & integer’image(e)
severity level;
end loop; — i
end procedure assert_equal;

In the spirit of Test-Driven Development, all the API elements are left empty, except for the pgm_read function which has to return something. I’ve created a “constant” empty_image for it to return (except it has to be a variable as it has an access type in it).
variable empty_image : image := (width => 0, height => 0, pixels => new pixel_vector(0 to -1);

Note the slightly wacky syntax to create an array of zero length: (0 to -1)

With the results:

libv.vhd:55:9:@0ms:(assertion error): PGM Width wrong. Got 0 expected 8(difference=-8)
libv.vhd:55:9:@0ms:(assertion error): PGM Height wrong. Got 0 expected 4(difference=-4)
libv.vhd:66:9:@0ms:(assertion error): PGM data length wrong. Got 0 expected 28(difference=-28)

Hurrah! It fails in the way we’d hope!

One comment

  1. Hi
    Good day
    I have a project title: divide the image into four parts and edge detection in parallel separated by a quad-core processor and eventually become a part of 4 episodes.
    I wrote a program that’s an image issue, but I do not know what Sobel edge through addimage and subimage How do I use and edges of finding you please help me and if it is possible to code them additional

Leave a comment

Your email address will not be published. Required fields are marked *