Files – theory & examples

VHDL provides mechanism to work with files. This feature is useful, when there is a need to store some data like test vectors, parameters or results of the simulation. Way of working with files in VHDL is very similar to other languages. File is treated as an object. It can be created in an architecture body, process or subprograms. To properly work with files, following steps should be done:

1. Define type of a file
2. Declare object of the defined file type
3. Open the file
4. Perform write/read operations
5. Close the file


1. Define type of a file

type file_type_name is file of type;

This command specifies what kind of data will be stored in a file. Type can be any scalar type (vector, integer etc.) or composite type (record and array, but only one-dimensional). Others types are forbidden.

During defining the new file type, implicitly are created procedures and functions, which give access to the files: file_open (two versions), read, write, endfile, file_close.

procedure file_open (
   file file_ptr     : file_type_name;
   file_path         : in string;
   open_kind         : in file_open_kind := read_mode
);
procedure file_open (
   status            : out file_open_status;
   file file_ptr     : file_type_name;
   file_path         : in string;
   open_kind         : in file_open_kind := read_mode
);
procedure read (
   file file_ptr     : file_type_name;
   value             : out data_type
);
procedure write (
   file file_ptr     : file_type_name;
   value             : in data_type
);
procedure file_close (
   file file_ptr     : file_type_name;
);

function endfile (file file_ptr : file_type_name) return boolean; 

Explanation of some types used in procedures:

  • parameter file_open_status

It is worth to notice that two different functions file_open are created. The difference is in one output – file_open_status. This output informs if there were any problems during opening the file. Possible values:

  • open_ok – everything is OK
  • status_error – file is already open
  • name_error
    • in read mode – files does not exist
    • in write mode – files can not be created
  • mode_error – file can not be open in a specified mode

Object of this type must be declared before it is used i.e.:

variable variable_name : file_open_status;

I encourage to use this version, because in case of any problems with opening the file, it is easier to find the reason of it.

  • parameter file_open_kind

file_open_kind describes access to a file:

  • read_mode
  • write_mode
  • append_mode
  • function endfile

Function endfile is very useful, cause it checks End Of File character. It returns true if checked value is not a declared file type.

2. Declare object of the defined file type

At this point, we know what kind of data the file stores. Now it is time to declare the object (of a given file type):

file file_ptr : file_type_name;

That declaration says that file_ptr object points to the file with data_type data.

3. Open the file

Opening the file means connecting object declared in point 2. with the file existing in the system. It can be done in two ways:

  • by extending declaration part:

file file_ptr : file_type_name open file_open_kind is file_path;

  • by using file_open procedure described in point 1. File_open procedure can be used only in statement part of the architecture.

What is the difference? First version opens the file during declaration. It still uses file_open procedure but this is done implicitly.

Second, explicit method, uses procedure file_open. File is open when that procedure is executed. What is an advantage? Before you open a file, you can do some other tasks.

4. Perform write/read operations

To do any operations on the file read/write procedures are used.

5. Close the file

Although file_close procedure is used implicitly, when architecture or subprogram is finished, always try to close the file when all operations are done.

After theory, time to examples

Example 1: read/write integers to the files

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ReadIntFromFile is
end ReadIntFromFile;

architecture ReadIntFromFile_rtl of ReadIntFromFile is

   constant C_FILE_NAME_RD :string  := "./dat/ReadIntFromFileIn.dat";
   constant C_FILE_NAME_WR :string  := "./dat/ReadIntFromFileOut.dat";
   constant C_CLK          :time := 10 ns;
   
   signal clk              :std_logic := '0';
   signal rst              :std_logic := '0';
   signal data             :integer := 1;
   signal eof              :std_logic := '0';

   type INTEGER_FILE is file of integer;
   file fptrrd             :INTEGER_FILE;
   file fptrwr             :INTEGER_FILE;

begin

ClockGenerator: process
begin
   clk <= '0' after C_CLK, '1' after 2*C_CLK;
   wait for 2*C_CLK;
end process;

rst <= '1', '0' after 100 ns;

GetData_proc: process

   variable statrd : FILE_OPEN_STATUS;
   variable statwr : FILE_OPEN_STATUS;

   variable varint_data    :integer := 1;

begin

   data      <= 1;
   eof       <= '0';

   wait until rst = '0';

   file_open(statrd, fptrrd, C_FILE_NAME_RD, read_mode);
   file_open(statwr, fptrwr, C_FILE_NAME_WR, write_mode);

   while (not endfile(fptrrd)) loop
      wait until clk = '1';
      read(fptrrd, varint_data);
      write(fptrwr, varint_data);
      data  <= varint_data;
   end loop;
   wait until rising_edge(clk);
   eof       <= '1';
   file_close(fptrrd);
   file_close(fptrwr);
   wait;
end process;

end ReadIntFromFile_rtl;

Example 2: read/write vectors to the files

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ReadVecFromFile is
end ReadVecFromFile;

architecture ReadVecFromFile_rtl of ReadVecFromFile is

   constant C_FILE_NAME_RD :string  := "./dat/ReadVecFromFileIn.dat";
   constant C_FILE_NAME_WR :string  := "./dat/ReadVecFromFileOut.dat";
   constant C_CLK          :time := 10 ns;

   signal clk              :std_logic := '0';
   signal rst              :std_logic := '0';
   signal data             :std_logic_vector(4-1 downto 0);
   signal eof              :std_logic := '0';

   type STD_FILE is file of std_logic_vector(4-1 downto 0);
   file fptrrd             :STD_FILE;
   file fptrwr             :STD_FILE;

begin

ClockGenerator: process
begin
   clk <= '0' after C_CLK, '1' after 2*C_CLK;
   wait for 2*C_CLK;
end process;

rst <= '1', '0' after 100 ns;

GetData_proc: process

   variable statrd         :FILE_OPEN_STATUS;
   variable statwr         :FILE_OPEN_STATUS;

   variable varstd_data    :std_logic_vector(4-1 downto 0);

begin

   data      <= (others => '0');
   eof       <= '0';

   wait until rst = '0';

   file_open(statrd, fptrrd, C_FILE_NAME_RD, read_mode);
   file_open(statwr, fptrwr, C_FILE_NAME_WR, write_mode);

   while (not endfile(fptrrd)) loop
      wait until clk = '1';
      read(fptrrd, varstd_data);
      write(fptrwr, varstd_data);
      data <= varstd_data;
   end loop;
   wait until rising_edge(clk);
   eof       <= '1';
   file_close(fptrrd);
   file_close(fptrwr);
   wait;
end process;

end ReadVecFromFile_rtl;

Example 3: read/write integers to the files, but with use extended declaration part instead of file_open procedure

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ReadIntFromFileImpOpen is
end ReadIntFromFileImpOpen;

architecture ReadIntFromFileImpOpen_rtl of ReadIntFromFileImpOpen is

   constant C_FILE_NAME_RD :string  := "./dat/ReadIntFromFileIn.dat";
   constant C_FILE_NAME_WR :string  := "./dat/ReadIntFromFileOut.dat";
   constant C_CLK          :time := 10 ns;
   
   signal clk              :std_logic := '0';
   signal rst              :std_logic := '0';
   signal data             :integer := 1;
   signal eof              :std_logic := '0';

   type INTEGER_FILE is file of integer;
   file fptrrd             :INTEGER_FILE open READ_MODE is C_FILE_NAME_RD;
   file fptrwr             :INTEGER_FILE open WRITE_MODE is C_FILE_NAME_WR;

begin

ClockGenerator: process
begin
   clk <= '0' after C_CLK, '1' after 2*C_CLK;
   wait for 2*C_CLK;
end process;

rst <= '1', '0' after 100 ns;

GetData_proc: process

   variable varint_data    :integer := 1;

begin

   data      <= 1;
   eof       <= '0';

   wait until rst = '0';

   while (not endfile(fptrrd)) loop
      wait until clk = '1';
      read(fptrrd, varint_data);
      write(fptrwr, varint_data);
      data  <= varint_data;
   end loop;
   wait until rising_edge(clk);
   eof       <= '1';
   file_close(fptrrd);
   file_close(fptrwr);
   wait;
end process;

end ReadIntFromFileImpOpen_rtl;

* When you simulate given examples, you will see that codes do not behave as you would expect. In next post I am going to explain why.

* I described theory of working with files with VHDL. Those are basics, but it does not mean that it is the simplest method to work with files. It just describes how it is done. In next posts I am going to describe more convenient and simpler ways to work with files.

*** *** ***

All source codes used in that post you can find on gitlab.com.

*** *** ***

2 thoughts on “Files – theory & examples”

  1. how to read from a file and write the output to the same file? suppose input is 111 and output is 0 so finally the input file must have 1110 in it?

    1. VHDL LRM in “File operations” chapter, defines three access modes to file objects: READ_MODE, WRITE_MODE, APPEND_MODE (for details please refer to LRM).
      None of them do what you want.
      Consider, if you need to write output data to the same file… I always prefer keeping results in a separated file.
      However if you really need to do that, I would suggest:
      1. write the output data to another file,
      2. remove the input file,
      3. rename output file to original input file name (from a system level, e.g. by script).
      I guess, this is the simplest, fastest and optimised way.

Leave a Reply

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