rising_edge() vs process sensitivity list

2.8k Views Asked by At

I've been developing in VHDL for a while in a University course and I thought that I understood how it worked, but once in a while I realize that I quite not actually understand it.

Here goes my question:

As I could understand, if a signal is in a process's sensitivity list, the process will "execute" whenever that signal changes value.

So I ask, what is the difference between these 2 pieces of code:

process(clk) is
begin
  if(clk = '1') then
      --Do Something
  end if;
end process;

and

process(clk) is
begin
   if(rising_edge(clk)) then
      --Do Something
   end if;
end process;

Shouldn't they behave equally?

5

There are 5 best solutions below

3
Matthew Taylor On

With the first, if clk changes from anything except '1' (eg 'H') to '1', "something" will get done, whereas with the second it won't. Adding an asynchronous reset illustrates this. You need:

process (clk, reset) is
begin
   if reset = '1' then
      --Reset something
   elsif rising_edge(clk) then
      --Do Something
   end if;
end process;

otherwise, "something" would get done when reset changed from '1' to '0', for example.

10
grorel On

Simulation:

Let's see how VHDL signals value are defined in VHDL. You'll find theses definitions in ieee.std_logic_1164 library.

Usually, signals are declared as std_logic which is the resolved subtype of std_ulogic defined as follow :

  type STD_ULOGIC is ( 'U',             -- Uninitialized
                       'X',             -- Forcing  Unknown
                       '0',             -- Forcing  0
                       '1',             -- Forcing  1
                       'Z',             -- High Impedance   
                       'W',             -- Weak     Unknown
                       'L',             -- Weak     0       
                       'H',             -- Weak     1       
                       '-'              -- Don't care
                       );

We can see that this kind of signals can have several others value than the usual '0' and '1'. The difference between your two processes lays here.

Let's see now how the rising_edge function is defined, always in the std_logic_1164 library :

  function rising_edge (signal s : STD_ULOGIC) return BOOLEAN is
  begin
    return (s'event and (To_X01(s) = '1') and
            (To_X01(s'last_value) = '0'));
  end function rising_edge;

  function To_X01 (s : STD_ULOGIC) return X01 is
  begin
    return (cvt_to_x01(s));
  end function To_X01;

  ----------------------------------------------------------
  -- table name : cvt_to_x01
  --
  -- parameters :
  --        in  :  std_ulogic  -- some logic value
  -- returns    :  x01         -- state value of logic value
  -- purpose    :  to convert state-strength to state only
  --                  
  -- example    : if (cvt_to_x01 (input_signal) = '1' ) then ...
  --
  ----------------------------------------------------------
  constant cvt_to_x01 : logic_x01_table := (
    'X',                                -- 'U'
    'X',                                -- 'X'
    '0',                                -- '0'
    '1',                                -- '1'
    'X',                                -- 'Z'
    'X',                                -- 'W'
    '0',                                -- 'L'
    '1',                                -- 'H'
    'X'                                 -- '-'
    );

This function actually convert the signal value to 'X' or '0' or '1'. And the function is true only when the converted new value is '1' and the converted last value was '0'.

Then the rising_edge function is true only for the following couples of [last_value;value] :

  • [0;1]
  • [L;1]
  • [0;H]
  • [L;H]

all other conditions won't be valid.

Synthesis:

[edited to remove false info]

As explained by @user1155120 in the prime post comments :

Lacking signal assignment neither process produce simulation events. Lacking an assignment target the first produces no level sensitive sequential logic (transparent latch) in synthesis. Lacking an assignment target the second produces no edge triggered sequential logic (register) in synthesis. Unlike grorel's Quartus prime other synthesis tools aren't guaranteed to produce a register for his output1. See IEEE Std 1076.6-2004 (withdrawn) 6.1.2.1 Rising (positive) edge clock., 6.2.1.1 Level-sensitive storage from process with sensitivity list (requires input signals in sensitivity list).

with output1 generated like this :

process(clk) is
begin
  if(clk = '1') then
      output1 <= input1;
  end if;
end process;

You must use an edge detection in your processes to make sure that registers are well created.

2
Renaud Pacalet On

On a pure simulation semantics point of view, your first form is a real rising edge detector if and only if:

  1. there is only clk in the sensitivity list (your case)
  2. and the type of clk is bit
  3. and clk is not declared with initial value '1' like in:

    signal clk: bit := '1';
    

If this holds, your -- do something will be executed only on rising edges of the clk. To understand why we need to look at the equivalent process with wait statement (yes, sensitivity lists are just a short hand for more general processes):

signal clk: bit;
...
process is
begin
  if(clk = '1') then
      --Do Something
  end if;
  wait on clk;
end process;

If clk is of type bit, the simulator initializes it with the leftmost value of enumerated type bit at the beginning of the simulation. As bit is declared:

type bit is ('0', '1');

its leftmost value is '0' and clk is initialized to '0'. On the first execution of the process the if test fails and the process suspends on the wait statement. From now on, it will resume only on value changes of clk. If the value change is a falling edge ('1' to '0') the if test fails and the process suspends on the wait statement. If the value change is a rising edge ('0' to '1') the if test passes, your -- do something is executed and the process suspends on the wait statement.

Because the conditions I listed above are quite constraining (especially the two first ones), and because many logic synthesizer don't really do semantics analyses but syntactic analyses (they "recognize" synchronous processes if they match certain coding patterns), your second form with rising_edge is preferable. It is standard since a long time, enough for being supported by all logic synthesizers I know.

A bit more explanation about the condition "there is only clk in the sensitivity list": as explained by Matthew, as soon as you have more than one signal in the sensitivity list (asynchronous set or reset, for example) you must use something else to express the condition. The event signal attribute is a possibility:

process(clk, reset)
begin
    if clk = '1' and clk'event then

This really says that an event just occurred on clk and that the new value of clk is '1'. While with:

process(clk, reset)
begin
    if clk = '1' then

the if test passes if an event happens on reset while clk is high. Usually not what you want.

Most synthesizers will do what you want with if clk = '1' and clk'event but it is not the whole story. If the type of clk is not bit but a multi-valued type like std_ulogic, for instance, the test passes for any transition of clk that ends with '1'. Like 'X' to '1' or 'U' to '1'. Usually not what you want, at least during simulations. This is where the rising_edge function becomes handy: it does the right thing. It uses the current value of its signal parameter s, plus s'event and s'last_value. It returns true only for transitions that you would consider as a true rising edge:

'0' -> '1'
'L' -> '1'
'0' -> 'H'
'L' -> 'H'

Your simulations work as expected and all logic synthesizers are happy with that because it is one of the patterns they recognize.

0
Leo Echevarría On

Since all of the other answers focused on a more formal, simulation-oriented side of the issue, I'll just add that synthesis-wise, most tools do not care much about sensitivity lists nowadays, but the rising_edge function is still essential in order to infer sequential logic (i.e.: flip-flops).

0
Jim Lewis On

To understand what is happening here, consider the following code:

process(clk) is
begin
  if clk = '1' then
      AReg <= A ; 
  end if ;
end process ;

Very clearly from a simulation perspective, Clk changes due to the sensitivity list and Clk = '1' is tested, so this simulates as a flip-flop.

The problem is in synthesis. Many synthesis tools ignore sensitivity lists. In doing this, they effectively treat all inputs as if they were on the sensitivity list. Hence, the effective process to the synthesis tool becomes:

process(clk, A) is
begin
  if clk = '1' then
      AReg <= A ; 
  end if ;
end process ;

As a result, the synthesis tool creates a latch (aka level sensitive storage device) for this code.

Unfortunately methods, such as ignoring sensitivity lists are a fundamental part of some synthesis tool's algorithm and a part of their patent portfolio. As a result, asking them to change, is not going to get them to change. It would be a large investment on their part - changing code and potentially paying their competitors to license their patents (ok, patents may be expired by now) - for what? - a coding style that is not so great (further discussion below).

As a result, you need to add something to the if condition to imply an edge is present. Either rising_edge or using clk'event will get the job done.

process(clk) is
begin
  if clk = '1' and clk'event then
      AReg <= A ; 
  end if ;
end process ;

process(clk) is
begin
  if rising_edge(clk) then
      AReg <= A ; 
  end if ;
end process ;

My preference is rising_edge as it is easier to read but ... further discussion below.

Why did I say the coding style is "not so great". Well as @Renaud Pacalet pointed out, as soon as you need to add asynchronous control signals to the sensitivity list, such as asynchronous reset (probably the only one I have used), then you have to add rising_edge or 'event anyway.

Asynchronous reset looks like this:

process(clk, nReset)
begin
  if nReset = '0' then
    AReg <= '0' ; 
  elsif rising_edge(clk) then
      AReg <= A ; 
  end if;
end process ;

If we replace rising_edge with the code in rising_edge, it does this:

process(clk) is
begin
  if clk'event and (To_X01(clk) = '1') and (To_X01(cl'last_value) = '0') then
      AReg <= A ; 
  end if ;
end process ;

Don't do this in a synthesis tool as it probably does not work.

The question is though, do I need all of these checks? The use of TO_X01 makes it so that '0' and 'L' are both treated as equal and likewise for '1' and 'H'. It also checks that there is a change from 0/L to 1/H.

Since the simple check clk'event and clk ='1' works, the synthesis tool answer is no. I consider the checks in rising_edge when applied to clk, to be a valley girl check - they want to be for sure, for sure (check your 1980's California speak guide) that it is a rising edge. For clock, this sort of check at every place we have a flip-flop is excessively redundant - if we needed a check like this, it should be in one central spot rather than in each flip-flop. OTOH, in a testbench, if I am checking for a rising_edge on a non-clock signal, using rising_edge may be essential for the code being correct.

None the less, I like rising_edge as a function name because it is readable - and that is probably the most important thing to ensure in design today.

That leads my other curiosity. Is rising_edge slower in simulation or are simulators able to apply special optimizations to rising_edge (since it is in a package they know about) and maybe it is faster? I don't expect anyone to answer this as that sort of optimization would be in the secret domain of simulator vendors.