# How can I import a file's contents as an array of hexadecimal constants in VHDL?

I've written an assembler for my CPU architecture, and I'd like to be able to import its output as program memory in VHDL. Currently, the program memory is stored like this:

type PM_t is array(0 to 511) of unsigned(31 downto 0);
constant PM_c : PM_t := (
X"04000000",    -- dummy
X"08000000",    -- dummy
X"540007FE",    -- J 0
X"0C000000",    -- dummy
X"10000000",    -- dummy

others => (others => '0')
)


An equivalent output from my assembler would look like this:

04000000
08000000
540007FE
0C000000
10000000


How could I go about importing that output (which is stored in a file) into my VHDL code in a manner compatible with my current setup? I need this both for simulation and synthesis. And, I am using a Nexys 3.

• Given your target memory size (16kbit) you'd probably be wise to use a block memory primitive (block RAM or block ROM) in your FPGA to store this program code. That means using a vendor core instead of pure VHDL code. And the vendor cores typically include some core generator that will allow you to load initialization values. Apr 13 at 8:49
• Write a script to read from the text file and then generate the memory vhd file corresponding to it. Apr 13 at 9:46
• @Mr.Snrub In that case, would it be a good idea to specify what FPGA I'm using in the question? Apr 13 at 10:41
• @Mr.Snrub VHDL synthesizers can infer block RAM/ROM, but I would indeed prefer to explicitly use the block so that I know what I'm getting. If you are using Xilinx Vivado, on the left somewhere is a link that opens up a list of templates for things like block RAM/ROM. But it doesn't actually solve the question. Apr 13 at 11:51
• On Stackoverflow, an example of using an initialization function call to read a file. The filename can be passed as a generic. A hex_read or hread procedure can be extracted from open source ghdl (lines 1459 - 1554) for inclusion in user provided design units for tools compliant to revisions earlier than -2008. Constants are less resource intensive than signals. Apr 13 at 15:20

One method that works independent of what synthesis tools you are using is to write a script/macro that takes your source data and converts it to a syntactically correct VHDL file that defines a constant for the data.

The VHDL file would be included as part of your project in whatever IDE you are using.

If you have Microsoft Excel, it's possible to write a VBA macro that can do it (or even just an Excel formula that generates the correct code that you can just paste into a file).

If you want to go to the next level, you could get a copy of the free version of Visual Studio and write a small console application that converts a data file into a VHDL file. I had to do something similar once to convert an intel HEX file into a constant array that was included in a C++ project.

Xilinx supports Spartan-6 in ISE only.

From UG 687 XST User Guide for Virtex-6, Spartan-6, and 7 Series Devices RAM Initial Contents, Specifying Initial Contents in an External Data File (P.221):

type RamType is array(0 to 127) of bit_vector(31 downto 0);
impure function InitRamFromFile (RamFileName : in string) return RamType is
FILE RamFile : text is in RamFileName;
variable RamFileLine : line;
variable RAM : RamType;
begin
for I in RamType’range loop
end loop;
return RAM;
end function;
signal RAM : RamType := InitRamFromFile("rams_20c.data")


Note the UG687 version shown is from ISE 13.1 March 1, 2011.

The shortcoming here from the present question is the lack of support for reading hex values from a text file.

There are several ways to address this. ISE's unisim library supports the conversion of hex to binary in unisim_PKG.vhd (line 795), which would require a read procedure of type string with a subtype indication with a length of 8. Also an hread procedure can be written or imported (there is at least one open source example available).

In all cases UG687 Chapter 3 VHDL Support lists the suprograms required to implement a ROM initialization routine where we also note support for Synopsys package std_logic_textio procedure hread. (The table of supported subprograms begins on P.66.)

There are useful embelishments that can be added to an initialization function such as an endfile test which can abort the for loop and file_close to allow re-entrant calls.

Note the UG687 VHDL InitRamFromFile impure function (P.221) doesn't demonstrate the requirement for package textio declarations be made visible (use std.textio.all;).

The memory array variable in the initialization function can also declare the object with an initial value of all zeros.

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

entity foo is
end entity;

architecture fum of foo is
constant rom_file_name: string := "rom_hex_file.txt";
use std.textio.all;  -- declaration of text
type PM_t is array(0 to 511) of unsigned(31 downto 0);
impure function init_rom_from_file (rom_filename: string) return PM_t is
FILE romfile: text is rom_filename;
variable rom: PM_t := (others => (others => '0'));-- zero fill
variable fileline: line;  -- std.standard
variable rom_element: std_ulogic_vector (31 downto 0);
begin
for i in PM_t'range loop
if endfile(romfile) then
exit;  -- the loop
end if;
readline (romfile, fileline);      -- one entry per line
rom(i) := unsigned(rom_element);
end loop;
file_close (romfile);
return rom;
end function;
Constant PM_c: PM_t := init_rom_from_file(rom_file_name);
begin
process
-- for revisions earlier than -2008:
function to_string (inp: unsigned) return string is
variable image_str: string (1 to inp'length);
alias input_str:  unsigned (1 to inp'length) is inp;
begin
for i in input_str'range loop
image_str(i) := character'VALUE(std_ulogic'IMAGE(input_str(i)));
end loop;
return image_str;
end function;
begin
for i in 0 to 12 loop -- arbitrary number of elements to show
-- zero filled array elements could be for i in PM_c'range loop
report LF & HT & "PM_c(" & integer'image(i) &") = "
& to_string(PM_c(i));
end loop;
wait;
end process;
end architecture;


With rom_hex_file.txt containing:

04000000
08000000
540007FE
0C000000
10000000


produces:

%: ghdl -a -fsynopsys foo.vhdl
%: ghdl -e -fsynopsys foo
%: foo
foo.vhdl:46:12:@0ms:(report note):
PM_c(0) = 00000100000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(1) = 00001000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(2) = 01010100000000000000011111111110
foo.vhdl:46:12:@0ms:(report note):
PM_c(3) = 00001100000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(4) = 00010000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(5) = 00000000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(6) = 00000000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(7) = 00000000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(8) = 00000000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(9) = 00000000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(10) = 00000000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(11) = 00000000000000000000000000000000
foo.vhdl:46:12:@0ms:(report note):
PM_c(12) = 00000000000000000000000000000000
%:


Notes:

1. the format or report statements prior to user specified output is implementation dependent with required elements.
2. The initial value of rom is supported by UG687 Chapter 3 VHDL Objects, VHDL Variables but not supported in the withdrawn standard IEEE Std 1076.6-2004 8.3.4.1 Object declarations (the standard was withdrawn due to the lack of vendor participation). If UG687 is in error a variable assignment (rom := (others => (others=> '0'));) immediately prior to the for loop in the initialization function could be used. The initial value in the variable declaration would be ignored if 1076.6 compliant.