0
\$\begingroup\$

I'm having a strange issue with a simple Vivado (2015.3) VHDL simulation.

This code:

library ieee;
use ieee.std_logic_1164.all;

entity pulse is
    port (
        d   : in  std_logic;
        clk : in  std_logic;
        q   : out std_logic );
end pulse;

architecture Behavioral of pulse is
    signal ff0 : std_logic := '0';
    signal ff1 : std_logic := '0';
begin
    process (clk, ff0, ff1)
    begin
        if rising_edge(clk) then
            ff0 <= d;
            ff1 <= ff0;
        end if;
        q <= ff0 and not ff1;
    end process;
end Behavioral;

produces this RTL schematic:

RTL schematic

The purpose is just to take an input pulse d, which may have a duration of multiple clock cycles, and output a single-cycle pulse q. Very basic. The simulation looks like this:

pulse simulation

If I change the code to only use process variables instead, the simulation fails:

(note: the code below is terrible practice. It is not something I do operationally, nor something I recommend. Having two if rising_edge(clk) clauses in a single process is just something I stumbled upon - and now I am trying to understand its implications, and why it creates a contradiction between the results of the behavioral simulation and the actual synthesis.)

library ieee;
use ieee.std_logic_1164.all;

entity pulse is
    port (
        d   : in  std_logic;
        clk : in  std_logic;
        q   : out std_logic );
end pulse;

architecture Behavioral of pulse is
begin
    process (clk)
        variable ff0 : std_logic := '0';
        variable ff1 : std_logic := '0';
    begin
        if rising_edge(clk) then
            ff0 := d;
        end if;
        if rising_edge(clk) then
            ff1 := ff0;
        end if;
        q <= ff0 and not ff1;
    end process;
end Behavioral;

simulation with variables

In the variable architecture, the variables are converted to signals since they are in separate clock condition clauses. So, the two implementations should literally be the same.

The pre-synthesis RTL schematics of both architectures are identical (as produced by Vivado, and shown above), so unless I am missing something simple, the simulations should be as well.


edit 1:

The post-synthesis schematics of both architectures are also identical. They both look like this: post-synthesis schematic

edit 2: I have confirmed that "Post-Synthesis Functional Simulations" produce identical results for both architectures.

For "Behavioral Simulations" (i.e. simulation before synthesis), my question still stands.


For completeness, here is the simulation code:

library ieee;
use ieee.std_logic_1164.all;

library UNISIM;
use UNISIM.VComponents.all;

entity pulse_sim is
end pulse_sim;

architecture Behavioral of pulse_sim is

    -- Sim component 
    component pulse is
    port (
        d   : in  std_logic;
        clk : in  std_logic;
        q   : out std_logic );
    end component;

    -- Test signals
    signal d   : std_logic := '0';
    signal clk : std_logic := '1';
    signal q   : std_logic := '0';

    -- Clock constants
    constant PERIOD : time := 10 ns;  -- 100 MHz

begin

    -- DUT instance
    UUT: pulse 
    port map (
        d   => d,
        clk => clk,
        q   => q );

    -- run clock
    clk <= not clk after PERIOD/2;

    -- test process
    process
    begin
        d <= '1' after 30 ns,
             '0' after 80 ns,
             '1' after 120 ns,
             '0' after 150 ns;
        wait;
    end process;

end Behavioral;

\$\endgroup\$
  • \$\begingroup\$ The two architectures aren't equivalent. Signals are scheduled for update in a projected output waveform (IEEE Std 1076-2008 10.5.2 Simple signal assignments, 10.5.2.2 Executing a simple signal assignment statement). With no after time_value (0 is assumed). Signal updates occur earlier in a simulation cycle than processes are resumed (14.7.3.4 Signal update, 14.7.5.3 Simulation cycle). For a 0 delay signal values are not available until a delta simulation cycle. That they produce identical schematics is due to signals and variables being treated identically in synthesis. \$\endgroup\$ – user8352 Jul 20 '18 at 21:28
  • \$\begingroup\$ @user8352 I definitely think you're on to something here. The last statement is not accurate though. Synthesis is only inferring unique registers here because of the two rising_edge(clk) checks. Otherwise, if I were to put both variable assignments inside the same "if rising_edge", of course they would be treated as typical variables and both ff0 and ff1 would be equal to d. Personally, I would replace your current answer with your comment above. It seems much more relevant to me. \$\endgroup\$ – Blair Fonville Jul 20 '18 at 22:08
  • \$\begingroup\$ I have provided no answer. \$\endgroup\$ – user8352 Jul 20 '18 at 22:48
  • \$\begingroup\$ @user8352 My mistake. I thought you were the other user####. Nevertheless, I think you have provided an answer - your comment seems to be a good explanation. \$\endgroup\$ – Blair Fonville Jul 20 '18 at 22:57
  • \$\begingroup\$ @user8352 Yes, I think I'm the one who is confused; not the simulator. The question can be summarized as: why does it work in synthesis (and subsequently post-synthesis simulation) and not in the behavioral simulation? If this contradiction exists here, in this simple example, it raises concerns that it could exist elsewhere when the design is more complex. \$\endgroup\$ – Blair Fonville Jul 21 '18 at 0:11
1
\$\begingroup\$

No tool errors as I see. Synthesis has generated exactly what you expect but simulation is dealing with the 2 flip flops differently in the 2 cases. In the signal version the simulator recognises (and treats) the clocks as being the same event. In the variable version the simulator sees clock edges as being different events. Because your latch for FF0 appears first in your code, the simulator operates on FF0 first and updates the FF0 Q in response to that clock edge. Now the simulator moves on to look at FF1, but the FF0 Q output (FF1 input) has already changed so the same clock edge now changes FF1 Q output in exactly the same simulation delta period. The AND gate output doesn't change because the 2 inputs are never different. You have created a situation where the simulation (legitimately) behaves differently to the design - not good. I never design with variables in RTL and in many places I have worked, are not allowed under company coding rules. They can always easily be avoided.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I’ll buy that. Thanks for the answer. \$\endgroup\$ – Blair Fonville Apr 20 '19 at 23:37
0
\$\begingroup\$

The problem is that you are using variables incorrectly for what you are trying to achieve. In VHDL, when you drive signals in a process, the signals will hold their values until the process exits, and whatever the FINAL assignment of that signal was as the process executes is what the value will hold.

Here's an example:

   signal signalA : integer := 4;

   SignalExampleProcess : process(clk)
    begin
      if(rising_edge(clk)) then
         signalA <= 0;            --Signal A value is still 4
         signalA <= signalA + 1;  --Signal A value is still 4
         signalA <= signalA + 1;  --Signal A value is still 4. This is the last assignment that "hit" signalA for this process
      end if; --rising_edge(clk)
    end process SignalExampleProcess;  -- Update signalA value to 5 upon process exit.

At the start of the process, signalA was the value 4 (this was the pre-load value in the signal declaration area). So at the end of this process, signalA will be equal to 5. After the next clock cycle, signalA will be 6. and so on. Let's look at the same example using variables:

   VariableExampleProcess : process(clk)
      variable variableA : integer := 4;
    begin
      if(rising_edge(clk)) then
        variableA := 0;                --variableA value is 0
        variableA := variableA + 1;    --variableA value is 1
        variableA := variableA + 1;    --variableA value is 2
      end if; --rising_edge(clk)
    end process VariableExampleProcess; --variableA holds last value upon process exit

Notice this time, variableA is equal to 2 at the end. Variables can contain intermediate values as the process gets parsed by the language interpreter. Note that if you look at variableA on a logic analyzer, you would only see it be the value 4 upon reset, and then the value 2 on the next clock cycle. The intermediate values are just used for DESCRIBING the intended logic behavior.

So back to your example:

architecture Behavioral of pulse is
begin
    process (clk)
        variable ff0 : std_logic := '0';
        variable ff1 : std_logic := '0';
    begin
        if rising_edge(clk) then
            ff0 := d;                --ff0's value will now be d, ff0 == d
        end if;
        if rising_edge(clk) then
            ff1 := ff0;              --ff1's value will now be ff0, ff1 == d
        end if;
        q <= ff0 and not ff1;   --ff1 and ff0 always contain the same value, q will always be low.
    end process;
end Behavioral;

I'm under the impression that you got a valid RTL schematic based on the fact that you have two "if(rising_edge(clk)) then" statements in one process. I can't remember if this is illegal or not in VHDL, but I would say it's unusual and probably testing the edge cases of Vivado. If I make it one "if(rising_edge(clk)) then" statement and stick both variable assignments, it gives me what I would expect: one register.

And for the record, ModelSim also behaves identically to your Vivado simulation:

enter image description here

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ "If I make it one "if(rising_edge(clk)) then" statement and stick both variable assignments, it gives me what I would expect: one register" ... Yes, I agree with that statement, which is why I wasn't testing it that way. I understand variables and the basics you addressed in your answer. However, what you wrote is really more of the golden rule to teach beginners. There are multiple ways that variables will infer registers. One of which is when a variable is assigned a value at the transition of another signal (clk in my case, for both), and then passed to a external signal. \$\endgroup\$ – Blair Fonville Jul 20 '18 at 21:12
  • \$\begingroup\$ I'm not understanding why having two "if rising_edge(clk)" statements should make a difference in the behavior. In my mind, the process is kicked off when the clk signal changes, and as the interpreter iterates through each line, when it comes to either "if rising_edge(clk)" statements it should return true, and thus both variable assignment statements would still execute. If both variable assignments execute, then my code comments I added would still hold true. So talk me through how you imagine this double rising_edge working and inferring two registers. Examples of this coding style? \$\endgroup\$ – user2913869 Jul 20 '18 at 21:22
  • \$\begingroup\$ "Examples of this coding style?" ... don't get me wrong - it's a terrible coding style, and not something I would do operationally. This is just a test. But it does synthesize, and it implements into hardware. I believe your comment in the OP explains why it fails in simulation. I think it works in synthesis because synthesis doesn't use the sensitivity list. So, when it gets to the second "if rising_edge", it does just that. \$\endgroup\$ – Blair Fonville Jul 20 '18 at 22:12
  • \$\begingroup\$ I think we can agree that something in Vivado has a bug. From working several years with Vivado, that is not the least bit surprising. You've introduced an exotic way to write a process, and Vivado is choking. It seems we just disagree on whether it's the synthesizer or the simulator that is malfunctioned. You seem to imply that the code should work as written, and that in fact it does (when built). I'm saying that we have two different simulators showing that it won't work, and it shouldn't work based on how you're using variables. I don't know where else to go from here, so have a nice day! \$\endgroup\$ – user2913869 Jul 20 '18 at 22:18
  • \$\begingroup\$ It sounds like you misunderstood me. I'm fully on-board with what you have done/written (hence "I believe your comment ... explains why it fails in simulation"). To me that is the answer; which I would happily accept, if you were to move it from the comment to an answer. So, I don't think it should work (as a simulation) as written - based on the details you have provided. I do think it should work in the fabric though (and it does), notwithstanding the terrible style. \$\endgroup\$ – Blair Fonville Jul 20 '18 at 22:23
0
\$\begingroup\$

I found that having two clock condition clauses inside of the process appears to confuse the simulator (even though it's not an issue for synthesis - which is why the Post-Synthesis simulation works).

If I change the process to:

process (clk)
    variable ffs : std_logic_vector(1 downto 0) := b"00";
begin
    if rising_edge(clk) then
        ffs := ffs(0) & d;
    end if;
    q <= ffs(0) and not ffs(1);
end process;

it works as expected.

Essentially, by merging the two std_logic variables into a std_logic_vector, I am able to update both values in a single if rising_edge(clk) then clause, and still maintain the register inference.

However, regarding the original question, I am still at a loss to understand why the Behavioral simulation conflicts with the RTL schematic. So, please consider the question still open.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Simulation isn't confused, synthesis only supports a subset of signal semantics. The VHDL standard doesn't define synthesis. Variables are of the domain of sequential statements while signals are not. Historically simulation preceded synthesis, which is expensive (in time, resources and cost) and post synthesis verification proves equivalency. There's a UG901-vivado-synthesis-examples.zip where non shared variables (prior to -2002) don't occur in synthesis eligible clocked processes. Xilinx isn't teaching you VHDL, but their preferred (safe) synthesis subset. \$\endgroup\$ – user8352 Jul 20 '18 at 22:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.