Search code examples
vhdlhdlromtest-bench

Add constant array element through test bench


I am currently learning ROM modeling using VHDL. Right now, I've modeled a 32x8 ROM and I've instantiated it as an empty cons array on my main module because I plan to import a file through the test bench code that contains the data per line. My question is, after importing and reading a line from the file, how are you supposed to add this signal the ROM component? My current code for my testbench so far is as follows:

library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.STD_LOGIC_ARITH.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
use STD.textio.all;
use IEEE.STD_LOGIC_textio.all;

entity ROM_tb is
end;

architecture bench of ROM_tb is

  component ROM
      Port ( 
             address : in STD_LOGIC_VECTOR (4 downto 0);
             data : out STD_LOGIC_VECTOR(7 downto 0));
  end component;

  signal address: STD_LOGIC_VECTOR (4 downto 0);
  signal data: STD_LOGIC_VECTOR(7 downto 0);
  signal content : STD_LOGIC_VECTOR(7 downto 0); -- signal file content holder

   -- file objects instantiation
  file file_VECTORS : text;

begin

  uut: ROM port map ( address => address,
                      data    => data );

  stimulus: process
  --file objects declarations
  variable f_LINE      : line; -- file pointer
  variable f_CONTENT    : STD_LOGIC_VECTOR(7 downto 0); -- file content holder
  variable f_i          : INTEGER := 0; -- ROM MA location index
  
  variable i : INTEGER := 0;
  
  begin
  
    file_open(file_VECTORS, "ROM-data.txt", read_mode);
    
    while not endfile(file_VECTORS) loop
        readline(file_VECTORS, f_LINE);
        read(f_LINE, f_CONTENT);
        
        content <= f_content;

        --ROM(f_i) <= content; I was hoping to do something like this...
        
        f_i := f_i + 1;
    end loop;
  
        while i < 32 loop
           wait for 35 ns;
           address <= conv_STD_LOGIC_VECTOR(i, 5);
           i := i+1;
        end loop;
        
    wait;
  end process;

end;

I instantiated the necessary file objects and processed a while loop which will read my 8-bit data per line. For every line, I plan to pass the variable content to a signal and have this signal store it to the ROM array and I am not sure how.


Solution

  • Without the entity and architecture for ROM nor the contents of ROM_data.txt your question and answer can't be validated. Besides a "ROM" contained in an array of signal or variable values in the undisclosed ROM architecture any place a tool suite would allow you to provide the initial value of a RAM or value of ROM programmatically (e.g. Xilinx, not Intel) you could also use an impure function to provide the value of a constant of the array type in a declaration:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;  -- to_integer instead of std_logic_arith conv_integer
    use ieee.math_real.all;   -- for ceiling, log2
    
    entity rom is  -- generic ROM with std_logic_vector address and output
        generic (  -- default values supplied for plugin to OPs usage
                filename:       string := "ROM_DATA.txt";
                rom_width:      natural := 8;
                rom_depth:      natural := 32
        );
        port ( 
            address:    in  std_logic_vector 
                (integer(ceil(log2(real(rom_depth)))) - 1 downto 0);
            data:       out std_logic_vector (rom_width - 1 downto 0)
        );
    end entity;
    
    architecture foo of rom is
        type rom_array is array
            (0 to rom_depth - 1) of std_logic_vector (rom_width - 1 downto 0);
        impure function initromfromfile (romfilename: in string) 
                    return rom_array is
            use std.textio.all;
            file romfile:  text open read_mode is romfilename; 
            variable romfileline:   line;
            variable vrom:           rom_array;
            variable rom_value:     bit_vector(7 downto 0);
        begin 
            for i in vrom'range loop  -- contents of file are ordered
                if endfile(romfile) then   -- file can be shorter than rom array
                    vrom(i) := (others => '0');
                else
                    readline(romfile, romfileline); -- 1 datum per line
                    read(romfileline, rom_value);
                    vrom(i) := to_stdlogicvector(rom_value);
                end if;
            end loop;
            return vrom;
        end function;
        constant romval: rom_array := initromfromfile(filename);
    begin
        data <= romval(to_integer(unsigned(address)));   -- read 
    end architecture;
    

    Here there are defaults for a generic ROM for width, depth and the initialization file name.

    For a file ROM_DATA.txt:

    11111110
    11101101
    11111010
    11001110
    11011110
    10101101
    10111110
    11101111
    

    that has fewer data values than the ROM array size the remaining values will be zero filled.

    The initialization is performed in the declaration of the object holding the ROM value (and array type).

    The functionality can be easily tested:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    entity rom_tb is
    end entity;
    
    architecture foo of rom_tb is
        signal address:     std_logic_vector (4 downto 0);
        signal data:        std_logic_vector (7 downto 0);
        -- for IEEE Std 1076 revisions earlier than 2008:
        function to_string (inp: std_logic_vector) return string is
            variable image_str: string (1 to inp'length);
            alias input_str:  std_logic_vector (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
    ROM0:
        entity work.rom  -- no generic map and generic values will default
            port map (
                address => address,
                data => data
            );
    ROM_DUMP:
        process
        begin
            for i in 0 to 2 ** address'length - 1 loop
                address <= std_logic_vector (to_unsigned(i, address'length));
                wait for 0 ns; -- delta cycle for address to update
                wait for 0 ns; -- delta cycle for data to update
                report "rom(" & integer'image(i) & ") = " & to_string(data);
            end loop;
            wait;
        end process;
    end architecture;
    

    When run:

    ghdl -a rom.vhdl
    ghdl -e rom_tb
    ghdl -r rom_tb
    ../../src/ieee/v93/numeric_std-body.vhdl:2098:7:@0ms:(assertion warning): NUMERIC_STD.TO_INTEGER: metavalue detected, returning 0
    rom.vhdl:80:13:@0ms:(report note): rom(0) = 11111110
    rom.vhdl:80:13:@0ms:(report note): rom(1) = 11101101
    rom.vhdl:80:13:@0ms:(report note): rom(2) = 11111010
    rom.vhdl:80:13:@0ms:(report note): rom(3) = 11001110
    rom.vhdl:80:13:@0ms:(report note): rom(4) = 11011110
    rom.vhdl:80:13:@0ms:(report note): rom(5) = 10101101
    rom.vhdl:80:13:@0ms:(report note): rom(6) = 10111110
    rom.vhdl:80:13:@0ms:(report note): rom(7) = 11101111
    rom.vhdl:80:13:@0ms:(report note): rom(8) = 00000000
    rom.vhdl:80:13:@0ms:(report note): rom(9) = 00000000
    rom.vhdl:80:13:@0ms:(report note): rom(10) = 00000000
    rom.vhdl:80:13:@0ms:(report note): rom(11) = 00000000
    rom.vhdl:80:13:@0ms:(report note): rom(12) = 00000000
    rom.vhdl:80:13:@0ms:(report note): rom(13) = 00000000
    rom.vhdl:80:13:@0ms:(report note): rom(14) = 00000000
    rom.vhdl:80:13:@0ms:(report note): rom(15) = 00000000
    ...
    rom.vhdl:80:13:@0ms:(report note): rom(31) = 00000000
    

    You can see the ROM was initialized and available at time 0 (after elaboration).

    The warning comes from address not having a default value that can be interpreted as a binary number (all 'U's) during initialization. Once address has been assigned the warning no longer occurs.

    The use of IEEE package numeric_std instead of Synopsys package std_logic_arith keeps in mind the IEEE package is maintained (and expanded over revisions) while the Synopsys package is not.

    The use of the variable of bit_vector allows the use std.standard.read without recourse to Synopsys package std_logic_textio.

    IEEE Std 1076-2008 and later revisions provide a std_logic_1164 package that provides a READ procedure compatible with std_logic_vector subtypes as well as octal and hex read procedures. Revisions -2008 and later also provide predefined to_string functions for all single dimensional array types as well as IEEE packages numeric_std_unsigned supplanting Synopsys package std_logic_unsigned.

    The generic rom entity declaration allows other sizes of ROM to be supported by providing values for the depth and width. The generic constant supplied for the filename allows multiple ROMs to be supported by using different file names.

    There are several different ways of providing initial values to ROMs and RAMs available for the tag on Stackoverflow by search.

    In general Read Only Memory (ROM) shouldn't be writable.