Monthly Archives: January 2011

Reading image files with VHDL part 3

Having written a function to read a PGM image, how do we use it?

Well, the testbench at the bottom of the pgm vhdl file has an example:

variable i : pixel\_array\_ptr;  
-- Without the transpose function, we would have to present the initialisation data in a non-intuitive way.  
constant testdata : pixel\_array(0 to 7, 0 to 3) := transpose(pixel\_array'(  
(000, 027, 062, 095, 130, 163, 198, 232),  
(000, 000, 000, 000, 000, 000, 000, 000),  
(255, 255, 255, 255, 255, 255, 255, 255),  
(100, 100, 100, 100, 100, 255, 255, 255))  
);  

Being by setting up a variable to store the image in. We also create a constant which contains the data we expect to read from the file. As explained previously we have to transpose the array in order for it to “look as expected” in the code (which is more important than an extra function call to my mind)

Then we can test and check:

-- test on a proper image  
i := pgm_read("testimage_ascii.pgm");  
assert i /= null report "pixels are null" severity error;  
assert_equal("ASCII PGM Width", i.all'length(1), 8);  
assert_equal("ASCII PGM Height", i.all'length(2), 4);  
assert_equal("ASCII PGM data", i.all, testdata);  

That simple!

Code for this series can be found on github

Reading image files with VHDL part 2

Having set up a library for reading images, let’s now go on to read an image in!

pgm_read Recall, we have a function:

impure function pgm_read (filename : string)
return pixel_array_ptr;
 So, some declarations - fairly self-explanatory: 

file pgmfile : text;
variable width, height : coordinate; -- storage for image dimensions
variable l : line; -- buffer for a line of text
variable s : string(1 to 2); -- to check the P2 header
variable ints : integer_vector(1 to 3); -- store the first three integers (width, height and depth)
variable int : integer; -- temporary storage
variable ch : character; -- temporary storage
variable good : boolean; -- to record whether a read is successful or not
variable count : positive; -- keep track of how many numbers we've read
variable empty_image : pixel_array_ptr := null; -- return this on error
variable ret : pixel_array_ptr; -- actual return value
variable x, y : coordinate; -- coordinate tracking
 We begin by opening the file in read mode and reading the first line: 

-- setup some defaults
width := 0;
height := 0;
file_open(pgmfile, filename, read_mode);
readline(pgmfile, l);
 Now, reading the header.. The P2 format PGM header is very simple: 

P2
# optional comments - next two ints are
# xsize and ysize
128 90
# maybe some more comments
# next int is the max value for pixel
255
 All the integers are separated by whitespace (may be space characters or linefeeds or carriage returns, we know not). This suits VHDL, as the 

textio library can read whitespace separated integers from a text file! It’s slightly tricky as we have to manage the cases where the values appear on separate lines. First, check the “P2-ness”:
read(l, s(1));
read(l, s(2), good);
if not good or s /= “P2” then
report “PGM file ‘”&filename&”‘ not P2 type” severity warning;
file_close(pgmfile);
return empty_image;
end if;
VHDL’s

textio is a bit different from in most other languages – we read a line from the file then read values from the line buffer. So, we read the first two characters and check them against “P2”. Now to read the next three integers:
allints : loop — read until we have 3 integers (width, height and colour depth).
line_reading:loop
readline(pgmfile, l);
exit when l.all(1) = ‘#’; — skip comments;
if l’length = 0 then
report “EOF reached in pgmfile before opening integers found”
severity warning;
file_close(pgmfile);
return empty_image;
end if;
number_reading: loop
read(l, ints(count), good);
exit number_reading when not good; — need to read some more from file
count := count + 1;
exit allints when count > ints’high;
end loop;
end loop;
exit when count > ints’high;
end loop;
— Now we have our header sorted. store it
width := ints(1);
height := ints(2);
We have three loops – the outermost

allints loop runs until count increments beyond the end of the ints array. The line_reading loop reads a line, checks for comment lines to skip them, passes non-comment lines onto the next loop: number_reading – this reads numbers from the line buffer until it fails or has filled the ints array. In the former case, the line_reading loop provides more data, otherwise, we drop out and store our data. Once we have the width and height we can allocate an array and read the pixels:
— now read the image pixels
x := 0;
y := 0;
— allocate storage
ret := new pixel_array(0 to width-1, 0 to height-1);
allpixels : loop
readline(pgmfile, l);
exit when l = null; — oh dear, something went wrong!
exit when l’length = 0; — more wrongness!
numbers: loop
read(l, int, good);
exit numbers when not good;
ret(x, y) := int;
exit allpixels when x = width-1 and y = height-1;
x := x + 1;
if x >= width then
x := 0;
y := y + 1;
end if;
end loop numbers;
end loop allpixels;
Again a nested loop structure, one for reading the lines from the file and another for extracting the integers from the linebuffer. The x and y coordinates are updated on each integer read to “scan” across and down the image. Once

x and y reach their terminal values, the loop exits.
assert (x = width-1 and y = height-1)
report “Don’t seem to have read all the pixels I should have”
severity warning;
return ret;
Finally, a little error checking and warning, and return the data.

Onwards, to making use of it… Code for this series can be found on github

Reading image files with VHDL part 1 (again)

Data storage

One of the things VHDL can do (contrary to popular belief :)) is 2-D arrays. So reading images into a 2-D array is a very natural way to store the data. We’ll create a package called pgm to keep all our image reading and writing code together:

package pgm is
  subtype coordinate : natural;
  subtype pixel is integer range 0 to 255;
  type pixel_array is array (coordinate range <>, coordinate range <>) of pixel;
  type pixel_array_ptr is access pixel_array;
  -- Function: transpose
  -- Useful for initialising arrays such that the first coordinate is the x-coord, but the initialisation can "look" like the
  -- image in question within the code
  function transpose (i : pixel_array) return pixel_array;

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.
  • Images are stored in a 2d array, with the x-coordinate as the first dimension, as is traditional (unless you use Matlab!)
  • We’ll use an access type for the pixel storage being returned from the image reading function pgm_read to allow us to dynamically allocate the storage based on how big the actual image is, rather than having to know at compile-time.
  • The transpose function is the best way I can come up with so far that allows us to create constants which represent images where the code looks like the image it represents. Without this an initialiser for a 4×2 image would look like this:

    constant c : pixel_array(0 to 3, 0 to 1) := ( (11, 12), (21, 22), (31, 32), (41, 42));
    

    A bit counter-intuitive, no? With the transpose function we can do:

    constant c : pixel_array(0 to 3, 0 to 1) := transpose(pixel_array'(
    (11, 21, 31, 41),(12, 22, 32, 42));
    

API

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 function to write an image record out to a file

    impure function pgm_read (filename : string) return pixel_array_ptr;
    procedure pgm_write (filename : in string; i : in pixel_array);
    end package pgm;

Testing

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”. We can also create an image from scratch 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 : pixel_array_ptr;  
    constant testdata : pixel_array(0 to 7, 0 to 3) := transpose(pixel_array'(  
(000, 027, 062, 095, 130, 163, 198, 232),  
(000, 000, 000, 000, 000, 000, 000, 000),  
(255, 255, 255, 255, 255, 255, 255, 255),  
(100, 100, 100, 100, 100, 255, 255, 255))  
);  
    variable blacksquare : pixel_array(0 to 7, 0 to 7) := (others => (others => 0));  
  begin -- process test1  
    -- test on a proper image  
    i := pgm_read("testimage_ascii.pgm");  
    assert i /= null report "pixels are null" severity error;  
    assert_equal("ASCII PGM Width", i.all'length(1), 8);  
    assert_equal("ASCII PGM Height", i.all'length(2), 4);  
    assert_equal("ASCII PGM data", i.all, testdata);

    -- make sure we return a non-image for the binary-style PGM file
    i := pgm_read("testimage.pgm");
    assert i = null report "Binary pixels should be null" severity error;

    -- Now create an image from scratch - a letter M
    blacksquare(1,1) := 255; blacksquare(5,1) := 255;
    blacksquare(1,2) := 255; blacksquare(2,2) := 255; blacksquare(4,2) := 255; blacksquare(5,2) := 255;
    blacksquare(1,3) := 255; blacksquare(3,3) := 255; blacksquare(5,3) := 255;
    blacksquare(1,4) := 255; blacksquare(5,4) := 255;
    blacksquare(1,5) := 255; blacksquare(5,5) := 255;
    blacksquare(1,6) := 255; blacksquare(5,6) := 255;
    pgm_write("test_write.pgm", blacksquare);
    report "End of tests" severity note;
    wait;
  end process test1;
end architecture test;

Aside (libv)

You can read about the non-standard looking assert_equal functions here.

Back to the tests

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. So it returns NULL – an empty image! With the results:

pgm.vhd:204:9:@0ms:(assertion error): pixels are null  

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

Onwards to actually reading and writing…

libv – assert_equal, str, etc.

procedure assert_equal (
    prefix'event : string;
     got, expected : integer; 
    level : severity_level := error) is 
begin 
  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; 

or indeed a similar function for `pixel_array`:

procedure assert_equal (
prefix : string;
expected, got : pixel_array;
level : severity_level := error) is
begin -- procedure assert_equal
    assert_equal(prefix & "(width)", expected'length(1), got'length(1), level);
    assert_equal(prefix & "(height)", expected'length(2), got'length(2), level);
    for y in expected'range(2) loop
       for x in expected'range(1) loop
          assert expected(x,y)=got(x,y)
            report prefix &"(" & str(x) & "," & str(y)  &")" & 
                str(expected(x,y)) & "/=" & str(got(x,y))
            severity level;
       end loop; -- x
    end loop; -- y
end procedure assert_equal;

You’ll notice I got bored of writing integer'image and wrote a str function which takes an integer and an optional length argument and returns a string, left-padded with spaces if the length argument is longer than the integer requires:

 function str (val : integer; length : natural := 0) return string is 
   constant chars_needed : natural := num_chars(val); 
   variable s : string(1 to max(length, chars_needed)) := (others => ' '); 
 begin -- function str 
    if length = 0 then 
       return integer'image(val); 
    end if; 
    if chars_needed > length then 
       report "Can't fit " & integer'image(val) & " into " 
              & integer'image(length) & " character(s) - returning full width"
              severity warning; 
       return integer'image(val); 
    end if; 
    s(s'high-(chars_needed-1) to s'high) := integer'image(val); 
    return s; 
end function str; 

Again, this can be a standard API and we can have a str function for other types; For example, boolean:

 function str (val : boolean; length:natural range 0 to 1 := 0) return string is 
 begin -- function str 
     if length = 0 then 
       return boolean'image(val); 
     end if; 
     if length = 1 then 
       if val then 
         return "T"; 
       else 
         return "F"; 
       end if; 
     end if; 
 end function str;

Oops

It’s been very kindly pointed out to me via email (by Tricky) that I was talking complete rubbish saying that VHDL is rubbish at 2d arrays.

What I should’ve said was “VHDL is rubbish at arrays of other arrays”, which is a subtle but critical difference! Arrays of other arrays are very useful (for example in register files), and the fact that the dimensions of one of the arrays must be known at compile time can make some things tricky to impossible. For example, a generic mux with a variable number of inputs can’t easily also have a generic number of bits on each input.

This has no bearing at all on the reading, storage and writing of images! The reason I wrote what I did stems back (I think!) to the fact that I first wrote an image reading library many years ago at work and it’s worked in a similar way ever since, Now the code I published the other day is completely new, written straight out of my head, but with my “ancient” prejudices attached to it.

When Tricky wrote to me saying what’s wrong with proper 2d arrays, the light bulb pinged and I spent several hours wandering the house kicking myself! Still, it’s good to learn new things, even if you thought you already knew about them – clearly I hadn’t got them ingrained enough!

So, I’ve created a new version of the page in question, which creates a much tidier image access library.

Sorry for any confusion caused!

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:

[vhdl]
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;


[/vhdl]

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

API

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

[vhdl]
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;
[/vhdl]

Testing

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!

[vhdl]
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 := (
000,001,002,003,005,006,007,
000,000,000,000,000,000,000,
255,255,255,255,255,255,255,
100,100,100,100,100,000,000
);
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));
wait;
end process test1;

end architecture test;
[/vhdl]

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.

[vhdl]
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;
[/vhdl]

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

[vhdl]
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;
[/vhdl]

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).
[vhdl]
variable empty_image : image := (width => 0, height => 0, pixels => new pixel_vector(0 to -1);
[/vhdl]

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!