What am I missing here? Sensitivity of a concurrent stateme

F

fpgabuilder

Guest
Specifically, what is the difference between the following two
snippets of code in my behavioral model? i is a genvar.

always@(*)
begin
adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
(pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;
end

assign adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
(pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;


pixel_cnt is declared as shortint. It is held in reset at the
beginning of the simulation. Its reset value is 0. An initial block
asserts a reset which holds the pixel_cnt in reset. The reset is
released by a clock event which also increments the pixel_cnt.

The adc_d in the procedural block does not get a valid value before
pixel_cnt is incremented whereas adc_c that is continuously assigned
does get a valid value derived from the pixel_cnt before it is
incremented. I haven't been able to explain this yet. Any thoughts?
 
On 2010-03-18 15:44:06 -0700, fpgabuilder said:

On Mar 18, 3:11 pm, fpgabuilder <parekh...@gmail.com> wrote:
Specifically, what is the difference between the following two
snippets of code in my behavioral model? i is a genvar.

always@(*)
   begin
   adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
             ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;
    end

assign  adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
              ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;



This brings me back to my question - what is the difference in implied
sensitivity list of a concurrent statement and that which is specified
to a procedural block?
There are subtle differences/bugs with always@*. I think one of them
has to do with functions on the RHS. This a lot of why System Verilog
added always_comb, which does what you expect it to.

Also, there can be a big difference between these two when there is
delay modeling in testbenches:

A)
reg a;
wire b;
assign b = #5 a;

B)
reg a;
reg b;
always @a b=a;

The first models inertial delay. If 'a' is a pulse narrower than the
delay (5 in the example), nothing will come out on b.
The second models transport delay and 'b' will always be a perfectly
delayed copy of 'a'

This can really confuse a lot of Verilog newbies.

David
 
On Mar 18, 3:11 pm, fpgabuilder <parekh...@gmail.com> wrote:
Specifically, what is the difference between the following two
snippets of code in my behavioral model? i is a genvar.

always@(*)
   begin
   adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
             ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;
    end

assign  adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
              ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;

pixel_cnt is declared as shortint.  It is held in reset at the
beginning of the simulation.  Its reset value is 0.  An initial block
asserts a reset which holds the pixel_cnt in reset.  The reset is
released by a clock event which also increments the pixel_cnt.

The adc_d in the procedural block does not get a valid value before
pixel_cnt is incremented whereas adc_c that is continuously assigned
does get a valid value derived from the pixel_cnt before it is
incremented.  I haven't been able to explain this yet.  Any thoughts?
shortint is a 2-state variable which is initialized to 0 by default.
Is this why there is not transition (from X to 0) which is likely to
be seen in a 4-state variable causing the procedural block to not
update the adc_d?

This brings me back to my question - what is the difference in implied
sensitivity list of a concurrent statement and that which is specified
to a procedural block?
 
On Mar 18, 10:44 pm, fpgabuilder <parekh...@gmail.com> wrote:

Specifically, what is the difference between the following two
snippets of code in my behavioral model? i is a genvar.

always@(*)
   begin
   adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
             ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;
    end

assign  adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
              ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;

[...]

shortint is a 2-state variable which is initialized to 0 by default.
Is this why there is not transition (from X to 0) which is likely to
be seen in a 4-state variable causing the procedural block to not
update the adc_d?
Yes, you got it.

This brings me back to my question - what is the difference in
implied
sensitivity list of a concurrent statement and that which is specified
to a procedural block?
It's not the "procedural block"; it's the @ event-wait.

As you corretly noticed, your inputs are already stable at time 0
before the always block begins to execute. Conseqently it stalls
at the @* until the first input change. @* is NOT a sensitivity
list in the VHDL sense; it's an "event control" that procedurally
waits until there's a change on one of its input values.

SystemVerilog correctly and sensibly mimics VHDL behaviour
with its always_comb construct. Not only does always_comb
get around the impure-function problem that David Rogoff
mentioned, it also has different execution semantics
than always@* - it does its event-wait at the END of
execution, not at the beginning. So the process executes
once at time 0, after variable initialization, just like
a VHDL process does. This allows initialization values
to propagate through the design as you would expect.
It is very, very likely that your tools already support
always_comb; use it in preference to always@*.

Continuous assigns have always done this correctly. Unlike
concurrent signal assignments in VHDL, a Verilog continuous
assign is *not* a shorthand syntax for a process; it has its
own rules that are designed to make combinational logic
behave as you would expect.
--
Jonathan Bromley
 
On Mar 19, 5:46 am, Jonathan Bromley <s...@oxfordbromley.plus.com>
wrote:
On Mar 18, 10:44 pm, fpgabuilder <parekh...@gmail.com> wrote:

Specifically, what is the difference between the following two
snippets of code in my behavioral model? i is a genvar.

always@(*)
   begin
   adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
             ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;
    end

assign  adc_d[(i+1)*DATA_WIDTH-1:(i*DATA_WIDTH)] = (pixel_cnt[0]) ?
              ~(pixel_cnt + i*NUM_OF_SENSOR_TAPS) :
              (pixel_cnt + i*NUM_OF_SENSOR_TAPS) ;

[...]

shortint is a 2-state variable which is initialized to 0 by default.
Is this why there is not transition (from X to 0) which is likely to
be seen in a 4-state variable causing the procedural block to not
update the adc_d?

Yes, you got it.

 > This brings me back to my question - what is the difference in
implied

sensitivity list of a concurrent statement and that which is specified
to a procedural block?

It's not the "procedural block"; it's the @ event-wait.

As you corretly noticed, your inputs are already stable at time 0
before the always block begins to execute.  Conseqently it stalls
at the @* until the first input change.  @* is NOT a sensitivity
list in the VHDL sense; it's an "event control" that procedurally
waits until there's a change on one of its input values.

SystemVerilog correctly and sensibly mimics VHDL behaviour
with its always_comb construct.  Not only does always_comb
get around the impure-function problem that David Rogoff
mentioned, it also has different execution semantics
than always@* - it does its event-wait at the END of
execution, not at the beginning.  So the process executes
once at time 0, after variable initialization, just like
a VHDL process does.  This allows initialization values
to propagate through the design as you would expect.
It is very, very likely that your tools already support
always_comb; use it in preference to always@*.

Continuous assigns have always done this correctly.  Unlike
concurrent signal assignments in VHDL, a Verilog continuous
assign is *not* a shorthand syntax for a process; it has its
own rules that are designed to make combinational logic
behave as you would expect.
--
Jonathan Bromley
Thanks Dave and John. Another key difference related to sensitivity
list as noted in Sutherland's book System Verilog for Design is that
always_comb's sensitivity list includes variables used in functions
without arguments whereas always@*'s sensitivity list only includes
those variables that are specified as arguments of a function. e.g.
below is quoted from the book.

always_comb begin
a2 = data << 1;
b2 = decode();
...
end

function decode; // function with no inputs
begin
case (sel)
2'b01: decode = d | e;
2'b10: decode = d & e;
default: decode = c;
endcase
end
endfunction
 
On Fri, 19 Mar 2010 11:36:19 -0700 (PDT), fpgabuilder wrote:

Another key difference related to sensitivity
list as noted in Sutherland's book System Verilog for Design is that
always_comb's sensitivity list includes variables used in functions
without arguments whereas always@*'s sensitivity list only includes
those variables that are specified as arguments of a function.
That's what I meant when I mentioned "the impure-function problem"
(I think you also alluded to it in your original post). It's
not quite as simple as that example would suggest, for
at least two reasons:

1)
If it were only a matter of functions without arguments
then the problem would not have existed in Verilog-2001,
where functions must have at least one input argument.
More generally: the problem relates to any expression
that is evaluated in a function, but does not appear
in the function's arguments. always@* is sensitive
only to the arguments of functions in the body; it
takes no notice of other expressions evaluated in
a function's body. always_comb correctly examines
the function body and determines ALL the contributing
expressions.
2)
If a function calls other functions, always_comb will
(again correctly) work its way down the function call
tree and determine *all* the expressions that contribute,
not just those in the top-level functions. always@*
does not do this.

I'm not sure what continuous assignment does with these
more complicated cases, but my guess is that it handles
them "correctly" in the same way that always_comb does.
--
Jonathan Bromley
 

Welcome to EDABoard.com

Sponsor

Back
Top