VHDL coding tips and tricks: file handling
Showing posts with label file handling. Show all posts
Showing posts with label file handling. Show all posts

Sunday, December 20, 2020

Image Processsing: RGB to Gray scale Converter in VHDL

    Implementing image processing algorithms in VHDL is a scary thing for many. Though I agree that its much more difficult to do it in VHDL than in a high level programming language like C, Matlab etc, it needn't be that scary.

    In this post I am going to share the code for a simple image processing algorithm - A RGB to Gray scale image converter. 

    There are many ways, from simple to complex, in which you can do this. I have done it in a way, which makes sense to me. Touching upon few topics related to this subject. For example reading the image data from a text file, storing it in RAM and accessing the data within the code and then manipulating them etc...

    I have used the standard Matlab image Lenna.bmp for this. The original image was 512*512*3 pixels in size. This takes a long time to load and run in Modelsim. So I first reduced its size to 1/8th of its original size, making it a 64*64*3 pixel image. Each pixel ranges from 0 to 255 and the dimension "3" indicates the presence of Red, Green and Blue components.

    VHDL text file operations aren't ideal for reading multiple pixels from the same row. So I converted the 3 Dimensional image data into a 1 Dimensional matrix. And then used dlmwrite Matlab command to write it to a text file named rgb.txt. This will be our input image.

    In Matlab, I manually converted the above RGB image into a gray scale image using the formula:

 Grayscale Image = ( (0.3 * R) + (0.59 * G) + (0.11 * B) ).

    The above grayscale pixels were converted to a 1-D matrix and written to a text file called gray.txt. This text file would be read by our VHDL testbench to verify that the results from our VHDL design is the same as the ideal result obtained from Matlab.

The Matlab program which I used for achieving all this is shared below:


I=imread('Lenna.bmp');  %read the image into memory

I=imresize(I,1/8);  %reduce the size by 8 times.

%convert image to 1-D array and write it to a text file.

dlmwrite('rgb.txt',reshape(I,64*64*3,1,1));

I4=double(I);   %convert it to double format

%convert rgb pixels to gray manually as per formula.

for i=1:64

    for j=1:64

        I2(i,j) = I4(i,j,1)*0.3 + I4(i,j,2)*0.59 + I4(i,j,3)*0.11;

    end

end

%the converted image is changed to 1-D and then written to a text file.

I3=reshape(I2,64*64,1);

dlmwrite('gray.txt',I3);


    The standard RGB format Lenna image, the resized version of it and the converted Grayscale image are shared below:




    Now that we have prepared the above basic data to work with, we can go and explore the VHDL designs. There are three VHDL files in this project:

1) im_ram.vhd:

    The input RGB image is internally stored in a RAM. The RAM is declared and initialized with the pixel values from the text file rgb.txt. The std.textio library is used, to do the file reading operations.


--RAM entity for storing the image.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--the below libraries are needed for reading text file.
library std;
use std.textio.all;

entity im_ram is
    generic(
        ADDR_WIDTH : integer := 16--Address bus size of the Image Ram.
        IM_SIZE_D1 : integer := 64--Size along Dimension 1
        IM_SIZE_D2 : integer := 64  --Size along Dimension 2
    );
    port (
        Clk : in std_logic;
        addr_in : in unsigned(ADDR_WIDTH-1 downto 0);   --Address bus to the Image Ram.
        rgb_out : out std_logic_vector(23 downto 0--24 bit RGB pixel output
    );
end im_ram;

architecture Behav of im_ram is

--custom array declaration.
type im_ram_type is array (0 to  IM_SIZE_D1*IM_SIZE_D2-1of std_logic_vector(23 downto 0);

--function for reading the image pixels from text file and use
--it to initialize the RAM.
impure function im_ram_initialize return im_ram_type is
    variable line_var : line;
    file text_var : text;
    variable pixel : integer;
    variable image_pixels : im_ram_type;
begin        
    --Open the file in read mode.
    file_open(text_var,"rgb.txt",read_mode);    
    while(NOT ENDFILE(text_var)) loop   --until end of file is reached
        for k in 1 to 3 loop    --through R, G and B.
            for j in 0 to IM_SIZE_D2-1 loop
                for i in 0 to IM_SIZE_D1-1 loop
                    readline(text_var,line_var);   --read one row. Each row contains one pixel.
                    read(line_var,pixel);   --From the read line, read the integer value.
                    --save the pixel in the RAM.
                    image_pixels(i*IM_SIZE_D2+j)(k*8-1 downto k*8-8) := std_logic_vector(to_unsigned(pixel,8));
                end loop;
            end loop;
        end loop;
    end loop;
    file_close(text_var); --close the file after reading.
    return image_pixels;    
end function;

--declare and initialize the image ram.
signal ram : im_ram_type := im_ram_initialize;

begin

--read the R,G and B pixels from RAM with the addr_in input.
rgb_out <= ram(to_integer(addr_in));

end architecture;


2) rgb2gray.vhd:

    This is the top level entity which converts the image from RGB format to Grayscale. The RAM block is instantiated as a component inside this entity. 


--Convert a internally stored RGB image into gray image.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity rgb2gray is
    generic(
        ADDR_WIDTH : integer := 16--Address bus size of the Image Ram.
        IM_SIZE_D1 : integer := 64--Size along Dimension 1
        IM_SIZE_D2 : integer := 64  --Size along Dimension 2
    );
    port (
        Clk : in std_logic;
        reset : in std_logic;   --active high asynchronous reset
        data_valid : out  std_logic;    --High when gray_out has valid output.
        gray_out : out unsigned(7 downto 0--8 bit gray pixel output
    );
end rgb2gray;

architecture Behav of rgb2gray is

    component im_ram is
        generic(
            ADDR_WIDTH : integer := 16--Address bus size of the Image Ram.
            IM_SIZE_D1 : integer := 64--Size along Dimension 1
            IM_SIZE_D2 : integer := 64  --Size along Dimension 2
        );
        port (
            Clk : in std_logic;
            addr_in : in unsigned(ADDR_WIDTH-1 downto 0);   --Address bus to the Image Ram.
            rgb_out : out std_logic_vector(23 downto 0--24 bit RGB pixel output
        );
    end component;

    signal rgb_out : std_logic_vector(23 downto 0);
    signal addr_in : unsigned(ADDR_WIDTH-1 downto 0);

begin

    --Instantiation of Image RAM. Internally stored image.
    image_ram : im_ram generic map(ADDR_WIDTH,  IM_SIZE_D1 ,IM_SIZE_D2)
        port map(Clk, addr_in, rgb_out);

    --Process to convert RGB to Gray image.
    CONVERTER_PROC : process(Clk,reset)
        --temperary variables
        variable temp1,temp2,temp3,temp4 : unsigned(15 downto 0);
    begin
        if(reset = '1'then    --active high asynchronous reset
            addr_in <= (others => '0');
            data_valid <= '0';
        elsif rising_edge(Clk) then
            --output is ready when the last address in the ram has reached.
            if(to_integer(addr_in) = IM_SIZE_D1*IM_SIZE_D2-1then  
                addr_in <= (others => '0');
                data_valid <= '0';
            else    --otherwise keep incrementing the address value.
                addr_in <= addr_in + 1;
                data_valid <= '1';  --indicates output is ready
            end if;
            --Gray pixel = 0.3*Red pixel + 0.59*Green pixel + 0.11*Blue pixel
            --the 24 bit value is split into R,G and B components and multiplied
            --with their respective weights and then added together.
            temp1 := "01001100" * unsigned(rgb_out(7 downto 0));        --(0.3 * R)  
            temp2 := "10010111" * unsigned(rgb_out(15 downto 8));       --(0.59 * G) 
            temp3 := "00011100" * unsigned(rgb_out(23 downto 16));  --(0.11 * B)
            temp4 := temp1 + temp2 + temp3;
            --Most significant bit of the LSB portion is added to the MSB portion. 
            --To round off the result.
            gray_out <= temp4(15 downto 8) + ("0000000" & temp4(7));
        end if;
    end process;

end architecture;


3) tb.vhd:

    This is the testbench for testing our main designs. The output image is received from the main design and compared with the actual result. Previously using Matlab, we have saved the actual result in a file called gray.txt .


--Testbench
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--the below libraries are needed for reading text file.
library std;
use std.textio.all;

--Testbench entity is always empty.
entity tb is
end tb;

architecture sim of tb is

    --the generic parameters are declared and initialized as constants here.
    constant IM_SIZE_D1: integer := 64;
    constant IM_SIZE_D2: integer := 64--we have a 64*64 image.
    constant ADDR_WIDTH: integer := 12--64*64=4096 which needs 12 bits.
    
    --this array is used to store the output gray pixels.
    type image_type is array (1 to  IM_SIZE_D1, 1 to  IM_SIZE_D2) of integer;
    signal image_pixels : image_type := (others => (others => 0));

    --component declaration.
    component rgb2gray is
        generic(
            ADDR_WIDTH : integer := 16;
            IM_SIZE_D1 : integer := 64;
            IM_SIZE_D2 : integer := 64
        );
        port (
            Clk : in std_logic;
            reset : in std_logic;
            data_valid : out  std_logic;
            gray_out : out unsigned(7 downto 0)
        );
    end component;

    --temperory signal declarations.
    signal Clk,reset,data_valid : std_logic := '0';
    signal gray_out : unsigned(7 downto 0);
    constant Clk_period : time := 10 ns;    --clock period.

begin

    --generate the clock signal.
    Clk <= not Clk after Clk_period/2;

    --Instantiate the Unit under test.
    UUT : rgb2gray generic map(ADDR_WIDTH, IM_SIZE_D1, IM_SIZE_D2)
            port map(Clk, reset, data_valid, gray_out);


    --Process where we apply inputs, read outputs and verify the result.        
    STIMULUS_PROC : process
        variable i,j : integer := 1;    --loop indices.
        variable line_var : line;
        file text_var : text;
        variable pixel : integer;
        variable error : integer := 0;  --this value should be zero at the end of simulation.
        variable diff : image_type := (others => (others => 0));    
    begin
        reset <= '1';
        wait for Clk_period;
        reset <= '0';   --reset is applied for one clock cycle.
        wait until data_valid = '1';    --wait for valid data at the output port.
        while(data_valid = '1'loop
            wait until (falling_edge(Clk)); --sample outputs at the falling edge of clock
            image_pixels(i,j) <= to_integer(gray_out);  --save pixel as integer.
            --generate indices to save the pixels in the correct place.
            if(j = IM_SIZE_D2) then
                j := 1;
                if(i = IM_SIZE_D1) then
                    i := 1;
                else
                    i := i+1;
                end if;
            else
                j := j+1;
            end if
            wait until (rising_edge(Clk));  --pause until rising edge of the clock 
        end loop;
        --all output gray pixels are read. Activate reset again.
        reset <= '1';

        --Now check if the results are the same as in Matlab
        --Open the file in read mode. gray.txt contains pixels calculated using Matlab.
        file_open(text_var,"gray.txt",read_mode);   
        while(NOT ENDFILE(text_var)) loop   --until end of file is reached
            for j in 1 to IM_SIZE_D2 loop
                for i in 1 to IM_SIZE_D1 loop
                    readline(text_var,line_var);   --read one row. Each row contains one pixel.
                    read(line_var,pixel);
                    --calculate the difference between actual gray pixels from Matlab
                    --and pixel values from our rgb2gray VHDL design.
                    diff(i,j) := abs(image_pixels(i,j) - pixel);
                    --If the difference is 2 or more then, we take it as an error
                    --and increment the variable 'error' by 1.
                    if(diff(i,j) > 1then
                        error := error+1;
                    end if;
                    wait for 1 ns;  --pause for 1 ns. 
                end loop;
            end loop;
        end loop;
        file_close(text_var); --close the file after reading.

        wait;   --wait eternally after finishing simulation.
    end process;

end architecture;   --End of Testbench


    Note that IM_SIZE_D1 and IM_SIZE_D2 are the number of rows and columns respectively of the image matrix. You can read it as a short form for "Image size along dimension 1 (or) 2".

     In the testbench we have a variable matrix called "diff". This matrix contains the absolute value  of difference between gray image pixels, from Matlab and VHDL. A difference of "1" is considered okay, as binary multiplications can cause some loss of least significant bits. But more than a difference of "1" is considered as an error. We can see that at the end of simulation, we didn't get any "error". 

    There is no address output from the top level entity rgb2gray. In our design, I thought it was unnecessary, because the pixel outputs were coming out in a sequential manner. So the testbench "knew" where to place each output pixel.

    I hope this post was helpful for you and gave you some ideas on how to do image processing using VHDL.

    You can Download the VHDL codes, images etc from Here.


Saturday, August 17, 2013

How to write the current simulation time to a file in VHDL

In the past I have written few posts about file reading and writing in VHDL. In this post, we will address a specific issue related with testbench i.e writing the current simulation time in to a file. This kind of information is some times very useful for testing and debugging your design. 

This can be done using the textio package in vhdl. Let me show an example.

library ieee;
use STD.textio.all; 
use IEEE.STD_LOGIC_TEXTIO.all; 
 
entity rand_gen is
end rand_gen;
 
architecture behavior of rand_gen is 

begin

process
    variable L: line;
    variable T: time;
    variable line_var : line;
    file text_var : text;
begin    
    file_open(text_var,"time_file.txt",write_mode);  --open the file for writing.
    for i in 1 to 10 loop
        write(line_var, string'("The current simulation time is :"));
        write(line_var, time'IMAGE(now));
        writeline(text_var,line_var);
        wait for 10000 ns;
    end loop;
    file_close(text_var); 
    wait;
end process;

end behavior;

After the above code is run for sufficient time, a file named time_file.txt will be created with the following contents:

The current simulation time is :0 ps
The current simulation time is :10000000 ps
The current simulation time is :20000000 ps
The current simulation time is :30000000 ps
The current simulation time is :40000000 ps
The current simulation time is :50000000 ps
The current simulation time is :60000000 ps
The current simulation time is :70000000 ps
The current simulation time is :80000000 ps
The current simulation time is :90000000 ps


The code simply runs a for loop for 10 times, with a delay of 10000 ns between each write operation.

The predefined function now returns the current simulation time. time'image(X) returns a string representation of X that is of type time.  

The present simulation time is normally needed, when we need to know at what time exactly a certain event happens in vhdl. A well written testbench can make the testing process much more easier and fun. 

Note :- The code was tested using Xilinx ISE 13.1. But it should work with other simulation tools as well.

Friday, January 13, 2012

Reading and writing real numbers using Files - Part 3

After the previous posts on file handling, now I have come up with another way to read and write files.

In this article I will show how to read a text file containing real numbers and store the square roots of these real numbers in another text file. The input file is named as "1.txt" and output file is named as "2.txt".

Contents of 1.txt:

12.23
34.4343
23.11
5.0
25.0
49.0
81.88
1000000.0
121.0
78.9

Contents of 2.txt:

3.497142e+00
5.868075e+00
4.807286e+00
2.236068e+00
5.000000e+00
7.000000e+00
9.048757e+00
1.000000e+03
1.100000e+01
8.882567e+00

VHDL code:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.MATH_REAL.ALL;
library std;
use std.textio.all;

entity file_handle is
end file_handle;

architecture Behavioral of file_handle is

type real_array is array(1 to 10) of real;

begin

process

variable line_var : line;
file text_var : text;
variable r : real_array;


begin        

       
   --Open the file in read mode.
   file_open(text_var,"1.txt",read_mode);
    --run the loop 10 times to read 10 real values from the file.
    for i in 1 to 10 loop
    --make sure its not the end of file.
    if(NOT ENDFILE(text_var)) then
     readline(text_var,line_var);   --read the current line.
      --extract the real value from the read line and store it in the variable.
     read(line_var,r(i));
    end if;
    end loop;
    file_close(text_var); --close the file after reading.
 
    --Write the square root values of variable 'r' to another file.
    file_open(text_var,"2.txt",write_mode);
    --run the loop 10 times to write 10 real values to the file.
    for i in 1 to 10 loop
      write(line_var,sqrt(r(i))); --sqrt is a fucntion for finding square root.
        writeline(text_var,line_var);
    end loop;
    file_close(text_var);

  wait;

end process;

end Behavioral;

This way of reading and writing files is very helpful in many situations. In my next post I will show an example based on this code snippet.

Thursday, February 17, 2011

File reading and writing in VHDL - Part 2

I have published a post on file reading and writing using VHDL before. You can access it here. As you know file manipulation will help you to verify your design more effectively at the debugging stage of your design.

For file operation we use the library named textio in the STD directory. This library contains the in built functions for reading and writing files.

Reading Files in VHDL:

The below code reads a set of binary numbers from the file named read.txt and put them into a 4 bit std_logic_vector signal. The text file used with the code can be downloaded from here.

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
use STD.textio.all; --Dont forget to include this library for file operations.

ENTITY read_file IS
END read_file;

ARCHITECTURE beha OF read_file IS

    signal  bin_value : std_logic_vector(3 downto 0):="0000";
   
BEGIN
   
   --Read process
    process
      file file_pointer : text;
        variable line_content : string(1 to 4);
      variable line_num : line;
        variable j : integer := 0;
        variable char : character:='0';
   begin
        --Open the file read.txt from the specified location for reading(READ_MODE).
      file_open(file_pointer,"C:\read.txt",READ_MODE);   
      while not endfile(file_pointer) loop --till the end of file is reached continue.
      readline (file_pointer,line_num);  --Read the whole line from the file
        --Read the contents of the line from  the file into a variable.
      READ (line_num,line_content);
        --For each character in the line convert it to binary value.
        --And then store it in a signal named 'bin_value'.
        for j in 1 to 4 loop       
            char := line_content(j);
            if(char = '0') then
                bin_value(4-j) <= '0';
            else
                bin_value(4-j) <= '1';
            end if;
        end loop;  
        wait for 10 ns; --after reading each line wait for 10ns.
      end loop;
      file_close(file_pointer);  --after reading all the lines close the file. 
        wait;
    end process;

end beha;

The above VHDL code can also be downloaded from here. I have commented the codes so that you can understand the flow of the code. The values read from the file are represented by a signal named bin_value. The simulation waveform is given below:

The main points to be noted are:
  • Declare file pointers and other variables required as given in the above code.
  • file_open() to open the file. Change the path of the file depending on where your file is stored.
  • Use read and readline functions.
  • Finally after everything is over, close the file using file_close.


Writing Files in VHDL:

  The following code is used for writing a file. Remember that these codes contain just examples and depending on what you have to write to the file the code should be changed. The code writes binary numbers from 0000 to 1111 to a file named write.txt


LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.std_logic_arith.ALL;
use STD.textio.all;

ENTITY write_file IS
END write_file;

ARCHITECTURE beha OF write_file IS
   
BEGIN
   
   --Write process
    process
      file file_pointer : text;
        variable line_content : string(1 to 4);
        variable bin_value : std_logic_vector(3 downto 0);
      variable line_num : line;
        variable i,j : integer := 0;
        variable char : character:='0';
   begin
        --Open the file write.txt from the specified location for writing(WRITE_MODE).
      file_open(file_pointer,"C:\write.txt",WRITE_MODE);     
        --We want to store binary values from 0000 to 1111 in the file.
      for i in 0 to 15 loop
        bin_value := conv_std_logic_vector(i,4);
        --convert each bit value to character for writing to file.
        for j in 0 to 3 loop
            if(bin_value(j) = '0') then
                line_content(4-j) := '0';
            else
                line_content(4-j) := '1';
            end if;
        end loop;
        write(line_num,line_content); --write the line.
      writeline (file_pointer,line_num); --write the contents into the file.
        wait for 10 ns;  --wait for 10ns after writing the current line.
      end loop;
      file_close(file_pointer); --Close the file after writing.
        wait;
    end process;

end beha;

The above code can also be downloaded from here. Any file writing code should have the following structure:

  • Declare file pointers and other variables required as given in the above code.
  • file_open() to open the file. Change the path of the file depending on where your file is stored.
  • Use write and writeline functions.
  • Finally after everything is over, close the file using file_close.
Note:- The codes are tested successfully using Xilinx Webpack 12.1. Note that file reading is a part of testbench design. In actual FPGA you cannot read or write files. But for functional verification of your design this is a must know.