Search code examples
vhdlmodelsim

VHDL: Assigning one std_logic_vector to another makes '1' turn to 'X'


I have a baffling problem.. As part of a buffering process I am assigning one std_logic_vector to another, by simply doing:

dataRegister <= dataRegisterBuf;

The process is synced to a clock. See here for the full process:

--! This process buffers the data register synced to sclk when state is state_bufferingToSclk and sets registerReady when done
SclkDomainBuffering: process(sclk)
variable step: natural := 0;
begin
    if (rising_edge(sclk)) then
        if (state = state_bufferingToSclk) then
            if (step = 0) then
                dataRegister <= dataRegisterBuf;
                step := 1;
            elsif (step = 1) then
                registerReady <= '1';
                step := 2; 
            end if;
        else
            step := 0;
            registerReady <= '0';
        end if;
    end if;
end process SclkDomainBuffering;

The problem is, when simulating this in Modelsim, dataRegister does not take the value of dataRegisterBuf, instead every '1' in the vector becomes 'X'. So for example if dataRegisterBuf is "00010", dataRegister becomes "000X0". I can't for the life of me figure out why. Here is a simulation showing it happening: https://i.sstatic.net/XEH5y.png

I have stepped through the entire code and I can't see anything out of the ordinary. At the time it happens, line 84 in the code above does indeed execute, and that is the only statement that is executed that has anything to do with the two registers in question as far as I can tell.


Solution

  • Here's a Minimal Complete and Verifiable example created from your question and comments:

    library ieee;
    use ieee.std_logic_1164.all;
    
    entity baffling_problem is
    end entity;
    
    architecture foo of baffling_problem is
        type state_type is (state_bufferingToClk, state_bufferingToSclk);
        signal state: state_type;  -- defaults to 'LEFT, state_bufferingToClk
        signal dataRegisterBuf:     std_logic_vector (31 downto 0) :=
                    (1 | 2 => '1', others => '0');
        signal dataRegister:     std_logic_vector (31 downto 0) := (others => '0');
        signal registerReady:       std_logic;
        signal sclk:                std_logic := '1';
    begin
    
        SclkDomainBuffering: process(sclk)
        variable step: natural := 0;
        begin
            if (rising_edge(sclk)) then
                if (state = state_bufferingToSclk) then
                    if (step = 0) then
                        dataRegister <= dataRegisterBuf;
                        step := 1;
                    elsif (step = 1) then
                        registerReady <= '1';
                        step := 2; 
                    end if;
                else
                    step := 0;
                    registerReady <= '0';
                end if;
            end if;
        end process SclkDomainBuffering;
        SOMEOTHERPROCESS:
        process (state)
        begin
            if state = state_type'LEFT then  -- other than state_bufferingToSclk
                dataRegister <= (others => '0');
            end if;
        end process;
    STIMULI:
        process
        begin
            wait for 20 ns;
            sclk <= '0';
            wait for 5 ns;
            sclk <= '1';
            wait for 0 ns;   -- state transitions in distinct delta cycle
            state <= state_bufferingToSclk;
            wait for 20 ns;
            sclk <= '0';
            wait for 5 ns;
            sclk <= '1';
            wait for 20 ns;
            wait;
        end process;
    
    end architecture;
    

    And this gives the behavior your describe:

    baffling_problem.png

    See IEEE Std 1076-2008 14.7.3 Propagation of signal values, 14.7.3.1 General:

    As simulation time advances, the transactions in the projected output waveform of a given driver (see 14.7.2) will each, in succession, become the value of the driver. When a driver acquires a new value in this way or as a result of a force or deposit scheduled for the driver, regardless of whether the new value is different from the previous value, that driver is said to be active during that simulation cycle. For the purposes of defining driver activity, a driver acquiring a value from a null transaction is assumed to have acquired a new value. A signal is said to be active during a given simulation cycle if
    — One of its sources is active.
    — One of its subelements is active.
    — The signal is named in the formal part of an association element in a port association list and the corresponding actual is active.
    — The signal is a subelement of a resolved signal and the resolved signal is active.
    — A force, a deposit, or a release is scheduled for the signal.
    — The signal is a subelement of another signal for which a force or a deposit is scheduled.

    So the signals (dataReady(1) and dataReady(2) are active their sources is active.

    An explanation of why their values are the resolved value of their drivers is found in 14.7.3.2 Driving values, none of the signals comprising dataReady are basic signals, see paragraph 3 f).

    And why you see the value of dataReady as "00000000000000000000000000000XX0" is described in 14.7.3.3 Effective values.

    The VHDL language describes how an elaborated design model is simulated as well as describing the syntax and semantics. An elaborated design model consists of processes described in a hierarchy interconnected by signals, and signals have history not just value. Signal updates are scheduled in projected output waveforms (see 10.5 Signal assignment statement).

    A lot of users just starting out in VHDL apply what they know of the behavior of other languages to VHDL, an example is the superfluous (but not forbidden) parentheses surrounding a condition in an if statement. Knowledge of other languages doesn't address signal behaviors (determined by the architecture of simulation models driven by simulation cycles.

    One of the things you'll note is that processes (11.3) suspend and resume based on explicit or implicit wait statements (10.2).

    All concurrent statements are elaborated into processes and or processes and block statements (11. Concurrent statements).

    Subprogram calls are either expressions (functions, 9.3.4) or statements (procedures, 10.7).

    No signal value is updated while any process that is scheduled to be active (those projected output waveforms matching the current simulation time, 14.7.4 Model execution, 14.7.3.4 Signal update).

    Signals driven in multiple processes represent multiple collections of hardware. The problem shows up because you've used resolved data types, if you had used unresolved data types you would have gotten an elaboration error instead (6.4.2.3 Signal declarations, paragraph 8). Resolved signals are allowed to have multiple drivers.

    The resolution table for std_logic elements is found in the package body for package std_logic_1164(See footnote 15 Annex A Description of accompanying files for access to the source of VHDL packages included with the standard). The resolution table will resolve a '0' and a '1' to an 'X'.

    And if all this sounds complex you can learn simple rules of thumb to prevent problems.

    In this case a rule of thumb would be to always drive a signal from a single process.