Electronic – VHDL – assigning array signal in a loop generates side effects

vhdl

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;

Best Answer

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
# Loading std.standard
# Loading std.textio(body)
# Loading ieee.std_logic_1164(body)
# Loading work.vhdl_loop_assignment(testing)
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
# Loading std.standard
# Loading std.textio(body)
# Loading ieee.std_logic_1164(body)
# Loading work.vhdl_loop_assignment(testing)
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 defined 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 specification 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.

Related Topic