Industrial manufacturing
Industrial Internet of Things | Industrial materials | Equipment Maintenance and Repair | Industrial programming |
home  MfgRobots >> Industrial manufacturing >  >> Industrial programming >> VHDL

Initializing FPGA Block RAM from Text Files Using VHDL’s TEXTIO Library

Populating block RAM with predefined values is most efficiently done by reading binary or hexadecimal literals from an ASCII file. This technique also serves to create ROMs, as FPGA ROM is simply a RAM that is only read from.

For the examples below, the following constants and RAM type are declared in the declarative region of the VHDL file:

constant ram_depth : natural := 256;
constant ram_width : natural := 32;

type ram_type is array (0 to ram_depth - 1)
  of std_logic_vector(ram_width - 1 downto 0);

This article is part of a series on the TEXTIO library in VHDL. Stimulus file read in testbench using TEXTIO | BMP file bitmap image read using TEXTIO

Reading Lines and Data with TEXTIO

The TEXTIO package, located in the standard std library, provides the subprograms required for file I/O. It is automatically available, so you only need to import it:

use std.textio.all;

Our RAM data will be stored in an ASCII file where each line represents one memory word. The READLINE procedure reads a single line into a LINE object. Its prototype is:

procedure READLINE (file F: TEXT; L: inout LINE);

type LINE is access STRING; -- pointer to a dynamically allocated string

VHDL‑2008 introduces OREAD, HREAD, and BREAD for extracting octal, hexadecimal, and binary values from a LINE object. For brevity we will focus on hexadecimal and binary reads. The relevant declarations are:

procedure HREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR;
                                  GOOD : out BOOLEAN);
procedure HREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR);

alias BREAD is READ [LINE, STD_ULOGIC_VECTOR, BOOLEAN];
alias BREAD is READ [LINE, STD_ULOGIC_VECTOR];

For legacy VHDL versions (pre‑2008), the standard READ procedure must be used, which does not support hexadecimal directly. In that case a custom conversion routine is required.

Loading Hexadecimal Data

Hexadecimal notation is convenient because each pair of hex digits maps directly to a byte. The sample ram_content_hex.txt file contains 256 lines, each holding an 8‑character hex string from 00000001 to 00000100:

1
2

255
256
00000001
00000002

000000FF
00000100

The following impure function reads the file and returns a fully initialized ram_type array. It works with VHDL‑2008 and newer:

impure function init_ram_hex return ram_type is
  file text_file : text open read_mode is "ram_content_hex.txt";
  variable text_line : line;
  variable ram_content : ram_type;
begin
  for i in 0 to ram_depth - 1 loop
    readline(text_file, text_line);
    hread(text_line, ram_content(i));
  end loop;
  return ram_content;
end function;

After defining the function, the RAM signal is instantiated with the initial content:

signal ram_hex : ram_type := init_ram_hex;

Legacy VHDL: Implementing HREAD Manually

When HREAD is unavailable (e.g., VHDL‑93 or VHDL‑2002), a manual conversion routine can be embedded in the initialization function. The following example demonstrates this approach:

impure function init_ram_hex return ram_type is
  file text_file : text open read_mode is "ram_content_hex.txt";
  variable text_line : line;
  variable ram_content : ram_type;
  variable c : character;
  variable offset : integer;
  variable hex_val : std_logic_vector(3 downto 0);
begin
  for i in 0 to ram_depth - 1 loop
    readline(text_file, text_line);
    offset := 0;
    while offset < ram_content(i)'high loop
      read(text_line, c);
      case c is
        when '0' => hex_val := "0000";
        when '1' => hex_val := "0001";
        when '2' => hex_val := "0010";
        when '3' => hex_val := "0011";
        when '4' => hex_val := "0100";
        when '5' => hex_val := "0101";
        when '6' => hex_val := "0110";
        when '7' => hex_val := "0111";
        when '8' => hex_val := "1000";
        when '9' => hex_val := "1001";
        when 'A' | 'a' => hex_val := "1010";
        when 'B' | 'b' => hex_val := "1011";
        when 'C' | 'c' => hex_val := "1100";
        when 'D' | 'd' => hex_val := "1101";
        when 'E' | 'e' => hex_val := "1110";
        when 'F' | 'f' => hex_val := "1111";
        when others =>
          hex_val := "XXXX";
          assert false report "Found non-hex character '" & c & "'";
      end case;
      ram_content(i)(ram_content(i)'high - offset
        downto ram_content(i)'high - offset - 3) := hex_val;
      offset := offset + 4;
    end loop;
  end loop;
  return ram_content;
end function;

Although a dedicated HREAD procedure could be defined, most synthesis tools (e.g., Xilinx Vivado) do not support procedures that accept a LINE argument. The following error illustrates this limitation:

In Vivado:
[Synth 8-27] Procedure argument of type ‘line’ is not supported [init_ram_tb.vhd:15]

Reading Binary Data

If the RAM width is not a multiple of 8, binary literals may be preferable. The sample ram_content_bin.txt file contains 256 lines, each with a 32‑bit binary string:

1
2

255
256
00000000000000000000000000000001
00000000000000000000000000000010

00000000000000000000000011111111
00000000000000000000000100000000

In VHDL‑2008, the BREAD procedure can be used to read binary values directly:

impure function init_ram_bin return ram_type is
  file text_file : text open read_mode is "ram_content_bin.txt";
  variable text_line : line;
  variable ram_content : ram_type;
begin
  for i in 0 to ram_depth - 1 loop
    readline(text_file, text_line);
    bread(text_line, ram_content(i));
  end loop;
  return ram_content;
end function;

Instantiate the RAM with:

signal ram_bin : ram_type := init_ram_bin;

Legacy VHDL: Binary Reads with READ

For VHDL‑2002 or VHDL‑93, the READ procedure must be used. Since it returns a bit vector, the result is converted to std_logic_vector via To_StdLogicVector:

impure function init_ram_bin return ram_type is
  file text_file : text open read_mode is "ram_content_bin.txt";
  variable text_line : line;
  variable ram_content : ram_type;
  variable bv : bit_vector(ram_content(0)'range);
begin
  for i in 0 to ram_depth - 1 loop
    readline(text_file, text_line);
    read(text_line, bv);
    ram_content(i) := To_StdLogicVector(bv);
  end loop;
  return ram_content;
end function;

Backporting Modern TEXTIO Features

If you prefer not to modify the legacy code, you can import the std_logic_1164_additions third‑party package. This library backports the HREAD and BREAD procedures to older VHDL versions, at the cost of an additional dependency.

For more detailed examples and further reading, see the accompanying articles in this series.

VHDL

  1. Creating a Breathing LED Effect Using a Sine Wave from Block RAM
  2. Reading BMP Bitmap Images in VHDL Testbenches with TEXTIO
  3. Driving VHDL Testbenches from External Stimulus Files with TEXTIO
  4. Implementing an AXI FIFO in Block RAM Using Ready/Valid Handshake
  5. Step‑by‑Step Guide: Extracting PLC Data with IIoT for Real‑Time Insights
  6. C++ File Handling: Mastering Open, Read, Write, and Close Operations
  7. Reading Files in Java with BufferedReader – A Practical Guide with Examples
  8. Master Python File Handling: Create, Read, Write, and Open Text Files with Ease
  9. How to Read and Write CSV Files in Python: A Comprehensive Guide
  10. Seamless PLC‑to‑Cloud Integration: Harnessing IoT for Real‑Time Data Retrieval