# VHDL - assigning array signal in a loop generates side effects

I do not understand why the following vhdl code does not simulate as I think it should.

test_pipe_1(0) is assigned at process pr1, but the simulation (both Aldec and GHDL) shows test_pipe_1(0) being 'U' all the time.

OTH, test_pipe_2(0) works differently in Aldec vs GHDL. In Aldec, the signal is assigned as expected ('Z' @ 0 sec, '0' @ 500 ns, '1' @ 1 us, etc).

In GHDL, the wierd behaviour is seen ('Z' @ 0 sec, 'U' @ 500 ns). There is no 'U' driver for test_pipe_2(0) signal.

My feeling is that this is language definition feature, but cannot understand what, and why.

Any ideas?

library ieee;
use ieee.std_logic_1164.all;

entity vhdl_loop_assignment is
end;

architecture testing of vhdl_loop_assignment is
signal test_d0: std_logic;
signal test_pipe_1: std_logic_vector(9 downto 0);
signal test_pipe_2: std_logic_vector(9 downto 0) := (others => 'Z');
signal clk: std_logic;
begin
test_1: process(clk)
begin
if rising_edge(clk) then
l: for n in 0 to 8 loop
test_pipe_1(n+1) <= test_pipe_1(n);
end loop;
-- test_pipe_1(0) <= 'Z'; -- If this is uncommented, starts working as expected.
end if;
end process;

test_2: process(clk'delayed(300 ns))
begin
if rising_edge(clk'delayed(300 ns)) then
l: for n in 0 to 8 loop
test_pipe_2(n+1) <= test_pipe_2(n);
end loop;
end if;
end process;

pr1: process(clk)
begin
if rising_edge(clk) then
if test_d0 /= '0' then
test_d0 <= '0';
test_pipe_1(0) <= '0';
test_pipe_2(0) <= '0';
else
test_d0 <= '1';
test_pipe_1(0) <= '1';
test_pipe_2(0) <= '1';
end if;
end if;
end process;

pr2: process
begin
clk <= '0';
wait for 500 ns;
clk <= '1';
wait for 500 ns;
end process;
end;

• You don't assign any value to test_pipe_1(0) that's why it shows as undefined. test_pipe_2 is given 'Z' value here: signal test_pipe_2: std_logic_vector(9 downto 0) := (others => 'Z'); – Roger C. Jun 6 '16 at 9:04
• test_pipe_1(0) is assigned at process pr1: test_pipe_1(0) <= '0'; – Topi Rinkinen Jun 6 '16 at 9:25
• Yes, but it is first assigned at the very same time that the other process is using it; therefore it might be undefined if the simulator is "processing" test_1 process first. Why are you not giving it an initial value as you do with signal_test_pipe_2? – Roger C. Jun 6 '16 at 9:36
• I know how to avoid the behavior. Now I am trying to understand why it does not work as I expect to. If I change for...loop (in test_1 process) to: test_pipe_1(1) <= test_pipe_1(0); test_pipe_1(2) <= test_pipe_1(1); .... test_pipe_1(9) <= test_pipe_1(8); it starts working differently. The question being why? BR, -Topi – Topi Rinkinen Jun 6 '16 at 9:47
• I didn't give initial value for test_pipe_1 (as vs _2) to pinpoint the difference in behaviour. – Topi Rinkinen Jun 6 '16 at 11:40

This is an interesting question. Something I intuitively understood, but didn't know the reason why. Thanks to your question, I researched and learned something new!

It does appear that test_pipe_1(0) has only 1 driver (in pr1). However, when I simulate your code in Modelsim (kudos for the MCVE!), I see 2 drivers: pr1 and test_1. For example:

# vsim -c work.vhdl_loop_assignment
# Start time: 07:28:12 on Jun 06,2016
VSIM 1> run 20 us
VSIM 2> drivers test_pipe_1(0)
# Drivers for /vhdl_loop_assignment/test_pipe_1(0):
#    U  : Signal /vhdl_loop_assignment/test_pipe_1(0)
#      1 : Driver /vhdl_loop_assignment/pr1
#      U : Driver /vhdl_loop_assignment/test_1
#


Now, when you uncomment the test_pipe_1(0) <= 'Z';, this drives all the values in test_pipe_1. And since test_pipe_1 is constructed from resolved types, the resolution function is invoked, and the 'Z' resolves with the driver in pr1. To clarify, here are your 2 cases:

Withouttest_pipe_1(0) <= 'Z';, you have the example above. Both a '1' and a 'U' are being driven on test_pipe_1(0), which resolves to a 'U'.

With test_pipe_1(0) <= 'Z';, you get the following drivers:

# vsim -c work.vhdl_loop_assignment
# Start time: 07:33:24 on Jun 06,2016
VSIM 1> run 20 us
VSIM 2> drivers test_pipe_1(0)
# Drivers for /vhdl_loop_assignment/test_pipe_1(0):
#    1  : Signal /vhdl_loop_assignment/test_pipe_1(0)
#      1 : Driver /vhdl_loop_assignment/pr1
#      Z : Driver /vhdl_loop_assignment/test_1
#


Now, you are getting a '1' and a 'Z' on test_pipe_1(0). And the resolution function resolves to a '1'. And this is why your code starts working.

As a quick aside, had you changes to std_ulogic_vector, your simulation would have failed to compile or failed during elaboration.

Finally, to the root cause: multiple drivers. The problem is that drivers are created during elaboration, and the indices in for ... loop constructs are not determined until execution. Thus the simulators creates a driver for every single element in test_pipe_1 in the test_1 process during elaboration. I admit, though I knew something was wrong, it wasn't clear to me why. I had to dig around to find out why.

From the VHDL LRM (Section 12.4.4):

Elaboration of a process statement proceeds as follows: a) The process declarative part is elaborated. b) The drivers required by the process statement are created. c) The initial transaction deﬁned by the default value associated with each scalar signal driven by the process statement is inserted into the corresponding driver.

So, first the process is elaborated. At this point, the simulator doesn't know which bits of test_pipe_1 are going to have drivers (the range of the for ... loop hasn't been evaluated yet). So, it creates all drivers required as per 12.4.4b.

Now, in VHDL LRM (Section 12.5):

There are three particular instances in which elaboration occurs dynamically during simulation. These are as follows:

a) Execution of a loop statement with a for iteration scheme involves the elaboration of the loop parameter speciﬁcation prior to the execution of the statements enclosed by the loop (see 8.9). This elaboration creates the loop parameter and evaluates the discrete range.

The for ... loop is elaborated 'prior to the execution' of the loop.

So, your fix is to 1) get rid of the for loop or 2) separate out the signals.

So, here's a tweak to your original post. I added a signal test_pipe_1_0_in to be the value that will go into test_pipe_1 (presuming this value and test_d0 are somehow unrelated, though in this example they clearly are related. You could use test_d0 instead and get the same result.).

library ieee;
use ieee.std_logic_1164.all;

entity vhdl_loop_assignment is
end;

architecture testing of vhdl_loop_assignment is
signal test_d0: std_logic;
signal test_pipe_1: std_logic_vector(9 downto 0);
signal test_pipe_1_0_in : std_logic;
signal clk: std_logic;
begin
test_1: process(clk)
begin
if rising_edge(clk) then
l: for n in 0 to 8 loop
test_pipe_1(n+1) <= test_pipe_1(n);
end loop;
test_pipe_1(0) <= test_pipe_1_0_in;
end if;
end process;

pr1: process(clk)
begin
if rising_edge(clk) then
if test_d0 /= '0' then
test_d0 <= '0';
test_pipe_1_0_in <= '0';
else
test_d0 <= '1';
test_pipe_1_0_in <= '0';
end if;
end if;
end process;

pr2: process
begin
clk <= '0';
wait for 500 ns;
clk <= '1';
wait for 500 ns;
end process;
end;


Hope that helps.

• the std_ulogic_vector for test_pipe_1 confirmed this (compile error). for loop isn't elaborated for index values, thus test_pipe_1 for all indices get driver in test_1 process. Funny part is that Altera Quartus synthesizes this as I was originally expecting (is it working in violation of LRM), and also GHDL had different simulation result to Aldec. Actually it could be fatal if you cannot count on simulation and synthesis results to be a functional match. – Topi Rinkinen Jun 7 '16 at 9:07
• @TopiRinkinen The differences between simulation and synthesis are indeed there. I ran some example code through both Synopsys and Vivado. Neither made a peep about multiple drivers. Yet Modelsim, Active-HDL, and Xsim (Vivado's simulator) all complain about multiple drivers. And I doubt you are the first to come across this issue. Yet my initial searches for information on this discrepancy between simulation and synthesis have yielded nothing. – PlayDough Jun 7 '16 at 14:09

With respect to the book VHDL Answers to Frequently Asked Questions, second edition, page 66:

initializing test_pipe_1 to 'Z' would solve the problem.

signal test_pipe_1: std_logic_vector(9 downto 0) := (others => 'Z');
signal test_pipe_2: std_logic_vector(9 downto 0) := (others => 'Z');

• Sadly google is blocking that page for me. Can you quote the relevant text from it in your answer? – Bruce Abbott Jun 4 '19 at 4:40
• Can you google the book (thanks to @Dave to edit the link)? – ManUnited10 Jun 5 '19 at 0:29