3

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?

Paebbels
  • 15,573
  • 13
  • 70
  • 139
user3013172
  • 1,637
  • 3
  • 15
  • 26
  • 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). –  Jun 18 '18 at 21:42
  • In the XST User Guilde there's a couple of section entitled Registers HDL Coding Techniques and Registers HDL Coding Techniques with both Verilog and VHDL examples also under VHDL Language Support there's a section on VHDL Sequential Circuits and a link to downloadable coding examples which follows the organization found in the XST UG. –  Jun 19 '18 at 05:47

4 Answers4

5

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.

grorel
  • 1,408
  • 15
  • 21
  • Surely you mean H, not W. – Matthew Taylor Jun 18 '18 at 14:42
  • As with the other answer, this does not address the two pieces of code doing something completely different in synthesis. Your answer would be appropriate if the question was "what is the difference between `rising_edge(clk)` and `clk = '1' and clk'event`". – scary_jeff Jun 18 '18 at 15:13
  • @scary_jeff Actually, Quartus generates the same RTL with both process... I didn't try with other compilator – grorel Jun 18 '18 at 15:22
  • To be complete, you should also have a `process(all)` with a `rising_edge(clk)`... – JHBonarius Jun 18 '18 at 15:50
  • Interesting, Quartus does the right thing there. Xilinx ISE 14.7 and Vivado 2017.2 both generate a latch for your first process. @JHBonarius if your process is only sensitive to `clk`, why would you use `all` ? – scary_jeff Jun 19 '18 at 07:45
  • @scary_jeff No: none of them is "correct". Both are trying to fix a user mistake and each has its own way. The user should stick to using the coding style described in the synthesis user guide. Only a subset of VHDL is synthesizable. – JHBonarius Jun 19 '18 at 08:35
  • 3
    @JHBonarius The issue is not whether it is synthesizable as all tools produce results - the issue is portability - some create latches (since they ignore sensitivity lists - or alternately view it as process(all)) and others create flip-flops (the intended result). Not alot we as users can do about this issue as my understanding is that this gets down into patented synthesis algorithms. – Jim Lewis Jun 19 '18 at 16:00
  • 1
    @JHBonarius Whose synthesis user guide are you espousing? IEEE's in the late 1076.6-2004, Xilinx's, Altera's, Synopsys', .... The problem is right down to the attributes, they are frustratingly similar, but different - even some have similar attribute names, but the object to which they are applied are different (type vs. signal). Maybe it is time for someone to revive 1076.6 and get the vendors to participate. – Jim Lewis Jun 19 '18 at 16:03
  • @JimLewis The one of the tool you are using (and the IEEE one is no more, as you are well aware of ;) ). Of course tool vendors should support every construct. However, in reality they only support certain constructs properly. I had a discussion once with a Xilinx representative, whom I asked why they don't support VHDL-2008 yet. They say it's just not worth the effort: customers are happy as it is, they will not get return on the extra investment. – JHBonarius Jun 19 '18 at 16:07
  • 2
    @JHBonarius Your conversation with Xilinx is called Marketing Driven language support. They reach this conclusion because people are not complaining enough. Hence, start complaining and filing bug reports. If we leave things as status quo, there is no portability between tools - not so bad for vendors - bad for the user community. Are Xilinx examples still using std_logic_arith and std_logic_unsigned? Since you suggested following their guides, do you also suggest using these? – Jim Lewis Jun 19 '18 at 16:42
3

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.

Matthew Taylor
  • 13,365
  • 3
  • 17
  • 44
3

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.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
0

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).