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.
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 instr
as 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).