Search code examples
vhdl

Reading a specific line from a *.txt file in VHDL


I am writing code that should somehow simulate an instruction memory read. The memory (in which data are stored in 1 byte long words) is represented by a *.txt file having 1 word per line; since instructions are 32 bit long I have to retrieve 4 words at the time, in order to concatenate them and build a complete instruction, to be sent as output. Such a memory is not accessed sequentially, but the instructions to be read may reside at any line of the file (as consequence to a jump or branch). As an example, the memory text file looks like this (the string "InstrN" isn't actually in the file; I wrote it here just to point out the distinction between subsequent instructions. The real file is made up only of 8 bits per line)

Instr1 11111111
       11111111
       11111111
       11111111
Instr2 00000000
       ........

Thus, the core of my work resides in the process to read from this file, for which I have written the following code:

read_instruction_memory : PROCESS(clk)
    FILE     mem           : TEXT; 
    VARIABLE mem_line      : LINE;
    VARIABLE read_byte     : STD_ULOGIC_VECTOR(7 DOWNTO 0);     -- byte of read memory
    VARIABLE line_counter  : INTEGER := 0;              -- counter to keep track of how many lines have already been read; it's initialized to 0 at the beginning of the process
    VARIABLE target_line   : INTEGER;                   -- line to be read, computed according to "pc" (program counter)
BEGIN 
    IF (clk'EVENT AND clk = '1') THEN 
        file_open(mem, "instruction_memory.txt", READ_MODE);
        target_line := to_integer(unsigned(pc));    
        WHILE line_counter < (target_line + 4) LOOP
            IF NOT endfile(mem) THEN                    -- discarded lines (don't correspond to the wanted lines)
                readline(mem, mem_line);
                IF line_counter >= target_line  THEN        -- subsequent four words are loaded
                    read(mem_line, read_byte);
                    instr(7+8*(line_counter - target_line) DOWNTO 8*(line_counter - target_line)) <= to_StdUlogicVector(read_byte);
                END IF;
            END IF;
            line_counter := line_counter + 1;
        END LOOP;
        file_close(mem);
    END IF;
END PROCESS read_instruction_memory;

Basicly, on the clock rising edge, the process enters a loop in which reads one line per iteration, disregarding it until it corresponds to one of the words of the instruction to retrieve; then, stores it into the proper portion of the instruction instr (from right to left in the vector). The fact that I open and close the file at each execution of the process implies that the read begins at the beginning of the file at each time, doesn't it?

The problem is that it doesn't work: when I start the simulation, the std_ulogic_vector results always undefined and I just can't understand why. Please, let me know whether it is an "algorithmic" error or it is just due to my poor understanding of I/O functions.


Solution

  • Creating an MCVE:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use std.textio.all;
    
    entity readinstr is
    end entity;
    
    architecture foo of readinstr is
        signal pc:      std_ulogic_vector (7 downto 0) := x"08";
        signal clk:     std_ulogic := '0';
        signal instr:   std_ulogic_vector (31 downto 0);
    
    begin
    read_instruction_memory: 
        process (clk)
            file     mem:            text; 
            variable mem_line:       line;
            variable read_byte:      std_ulogic_vector(7 downto 0);     -- byte of read memory
            variable line_counter:   integer := 0;              -- counter to keep track of how many lines have already been read; it's initialized to 0 at the beginning of the process
            variable target_line:    integer;                   -- line to be read, computed according to "pc" (program counter)
        begin 
            if clk'event and clk = '1' then 
                file_open(mem, "instruction_memory.txt", read_mode);
                target_line := to_integer(unsigned(pc));    
                while line_counter < target_line + 4 loop
                    if not endfile(mem) then                    -- discarded lines (don't correspond to the wanted lines)
                        readline(mem, mem_line);
                        if line_counter >= target_line  then        -- subsequent four words are loaded
                            read(mem_line, read_byte);
                            instr(7+8*(line_counter - target_line) downto 8*(line_counter - target_line)) <= to_stdulogicvector(read_byte);
                            report "read_byte = " & to_string(read_byte);
                        end if;
                    end if;
                    line_counter := line_counter + 1;
                end loop;
                file_close(mem);
            end if;
        end process read_instruction_memory;
    
    MONITOR:
        process (instr)
        begin
            if now > 0 ns then
                report "instr = " & to_string(instr);
            end if;
        end process;
    
    CLOCK:
        process
        begin
            wait for 10 ns;
            clk <= not clk;
            wait for 10 ns;
            wait;
        end process;
    end architecture;
    

    With a instruction_memory.txt:

    00000000
    00000001
    00000010
    00000011
    00010000
    00010001
    00010010
    00010011
    00100000
    00100001
    00100010
    00100011
    00110000
    00110001
    00110010
    00110011

    Reveals:

    ghdl -a --std=08 readinstr.vhdl
    ghdl -e --std=08 readinstr
    ghdl -r readinstr
    readinstr.vhdl:32:25:@10ns:(report note): read_byte = 00100000
    readinstr.vhdl:32:25:@10ns:(report note): read_byte = 00100001
    readinstr.vhdl:32:25:@10ns:(report note): read_byte = 00100010
    readinstr.vhdl:32:25:@10ns:(report note): read_byte = 00100011
    readinstr.vhdl:45:13:@10ns:(report note): instr = 00100011001000100010000100100000

    That your code appears to actually be doing the right thing.

    You close the file in the last statement of the if statement for the clock edge, and you open the file as the first statement of the if statement. That should work for successive clocks (which isn't tested in this MCVE).

    Signal updates don't occur until every pending process has resumed and suspended.

    The report statement for read_byte showed the correct values.

    (I removed some superfluous parentheses pairs).

    Basicly, on the clock rising edge, the process enters a loop in which reads one line per iteration, disregarding it until it corresponds to one of the words of the instruction to retrieve; then, stores it into the proper portion of the instruction instr (from right to left in the vector). The fact that I open and close the file at each execution of the process implies that the read begins at the beginning of the file at each time, doesn't it?

    Yes the file will be opened each clock, and 'scanned', the four lines represented by the PC are assembled to instras long as END_FILE does not return true.

    The problem is that it doesn't work: when I start the simulation, the std_ulogic_vector results always undefined and I just can't understand why. Please, let me know whether it is an "algorithmic" error or it is just due to my poor understanding of I/O functions.

    There's not enough of your design presented in your question to rule out algorithmic errors. There are a few things that might be done differently in the code you do show. We can also point to 'systemic' issues that might be cause errors due to not shown code.

    For instance detecting the clock edge using value and 'EVENT is subject to a false positive should you initialize clk to a '1'. The IEEE std_logic_1164 package contains two functions, rising_edge and falling_edge that require a transition between a zero ('L' or '0') and a one ('H' or '1') to detect an edge instead of just an event and a level.

    Looking at systemic issues also points out an actual problem in your process. Variable line_counter is not assigned a value of 0 every time there's a rising edge event in the process. A successive instruction will be fetched from the wrong place. line_counter would tell us the right PC value being read but it would fetch the first four bytes from instruction_memory.txt because line_counter isn't reset. This would show up by having the right line_counter value with the wrong read_byte values.

    Fixing that:

    architecture foo of readinstr is
        signal pc:              std_ulogic_vector (7 downto 0) := x"08";
        signal clk:             std_ulogic := '0';
        signal instr:           std_ulogic_vector (31 downto 0);
    
    begin
    
    read_instruction_memory: 
        process (clk)
            file     mem:            text; 
            variable mem_line:       line;
            variable read_byte:      std_ulogic_vector(7 downto 0); 
            variable line_counter:   integer := 0; -- INTIALIZSATION NOT NEEDED
            variable target_line:    integer;
        begin 
            if clk'event and clk = '1' then
                line_counter := 0;
                file_open(mem, "instruction_memory.txt", read_mode);
                target_line := to_integer(unsigned(pc)); 
                while line_counter < target_line + 4 loop
                    if not endfile(mem) then 
                        readline(mem, mem_line);
                        if line_counter >= target_line  then 
                        report "line_counter = " & integer'image(line_counter);
                            read(mem_line, read_byte);
                            instr(7 + 8 * (line_counter - target_line) downto 8 * (line_counter - target_line)) <= read_byte;
                            report "read_byte = " & to_string(read_byte);
                        end if;
                    end if;
                    line_counter := line_counter + 1;
                end loop;
                file_close(mem);
            end if;
        end process read_instruction_memory;
    
    POSITION_COUNTER:
        process (clk)
        begin
            if rising_edge(clk) then
                pc <= std_ulogic_vector(unsigned(pc) + 4);
            end if;
        end process;
    
    MONITOR:
        process (instr)
        begin
            if now > 0 ns then
                report "instr = " & to_string(instr);
            end if;
        end process;
    
    PC_MONITOR:
        process (pc)
        begin
            report "pc = " & to_string(pc);
        end process;
    
    CLOCK:
        process
        begin
            wait for 10 ns;
            clk <= not clk;
            wait for 10 ns;
            clk <= not clk;
            wait for 10 ns;
            clk <= not clk;
            wait for 10 ns;
            wait;
        end process;
    end architecture;
    

    And we get:

    ghdl -a --std=08 readinstr.vhdl
    ghdl -e --std=08 readinstr
    ghdl -r readinstr
    readinstr.vhdl:131:9:@0ms:(report note): pc = 00001000
    readinstr.vhdl:100:21:@10ns:(report note): line_counter = 8
    readinstr.vhdl:103:25:@10ns:(report note): read_byte = 00100000
    readinstr.vhdl:100:21:@10ns:(report note): line_counter = 9
    readinstr.vhdl:103:25:@10ns:(report note): read_byte = 00100001
    readinstr.vhdl:100:21:@10ns:(report note): line_counter = 10
    readinstr.vhdl:103:25:@10ns:(report note): read_byte = 00100010
    readinstr.vhdl:100:21:@10ns:(report note): line_counter = 11
    readinstr.vhdl:103:25:@10ns:(report note): read_byte = 00100011
    readinstr.vhdl:124:13:@10ns:(report note): instr = 00100011001000100010000100100000
    readinstr.vhdl:131:9:@10ns:(report note): pc = 00001100
    readinstr.vhdl:100:21:@30ns:(report note): line_counter = 12
    readinstr.vhdl:103:25:@30ns:(report note): read_byte = 00110000
    readinstr.vhdl:100:21:@30ns:(report note): line_counter = 13
    readinstr.vhdl:103:25:@30ns:(report note): read_byte = 00110001
    readinstr.vhdl:100:21:@30ns:(report note): line_counter = 14
    readinstr.vhdl:103:25:@30ns:(report note): read_byte = 00110010
    readinstr.vhdl:100:21:@30ns:(report note): line_counter = 15
    readinstr.vhdl:103:25:@30ns:(report note): read_byte = 00110011
    readinstr.vhdl:124:13:@30ns:(report note): instr = 00110011001100100011000100110000
    readinstr.vhdl:131:9:@30ns:(report note): pc = 00010000

    Which correctly shows fetching two consecutive instructions.

    Also note I removed a superfluous to_stdulogicvector conversion for read_byte.

    This doesn't get us any closer to your systemic issue but does allow speculation. Do you have a reset that's valid for multiple clocks? There's also the possibility you have multiple drivers for instr. Without an MCVE your reading audience is a bit in the dark.

    And all this also brings up another idea. How about initializing a memory array as an object from the file instruction_memory.txt, 'seeking' the position in a file is particularly cumbersome in VHDL.

    And yes there actually was an error in the read_instruction_memory process, revealed by modeling. line_counter wasn't reset before each execution of the while loop.

    There's a chance this would have been revealed in the process of creating an MCVE to duplicate the problem. And if not it would have become apparent to those answering your question with a little instrumentation (the report statements).