Featured post

Top 5 books to refer for a VHDL beginner

VHDL (VHSIC-HDL, Very High-Speed Integrated Circuit Hardware Description Language) is a hardware description language used in electronic des...

Thursday, 20 December 2012

Resume

Definition:

The action of a wait statement when the conditions for which the wait statement is waiting are satisfied.

Description

A suspended process (i.e. a process waiting for a condition specified in a wait statement to be met) is resumed when the condition is met. The execution of resumed process is started immediately in the current simulation cycle (time), unless the process is not postponed. In the latter case, the process execution is postponed to the last simulation cycle at the current simulation time.

A resumed process executes its statements sequentially in a loop until a wait statement is encountered. When this happens, the process becomes suspended again.

Examples

Example 1

process (CLK, RST)
begin
  if RST='1'
    then Q <= '0';
    elsif (CLK'event) and (CLK='1')
      then Q <= D;
  end if;
end process;

In this Example 1 of a D flip-flop, the process is sensitive to the two signals: CLK and RST. It will resume when any of the two signals will change its value. Resuming of the process will cause the execution of the 'if' statement (which his the only one statement in this process) and then the process will suspend again, waiting for a change on either RST or CLK.

Important Notes

· A resumed process not necessarily executes all its statements: if there are multiple wait statements the execution suspends on the next 'wait'.

Return Statement

Formal Definition

The return statement is used to complete the execution of the innermost enclosing function or procedure body.

Simplified Syntax

return;

return expression;

Description

The return statement ends the execution of a subprogram (procedure or function) in which it appears. It causes an unconditional jump to the end of a subprogram (example 1).

If a return statement appears inside nested subprograms it applies to the innermost subprogram (i.e. the jump is performed to the next end procedure or end function clause).

This statement can only be used in a procedure or function body. The return statement in a procedure may not return any value, while a return in a function must return a value (an expression) which is of the same type as specified in the function after the return keyword (example 2).

Examples

Example 1

procedure RS ( signal S, R: in BIT; signal Q, NQ: inout BIT) is
begin
  if (S = '1' and R = '1') then
    report "forbidden state: S and R are equal to '1'";
    return;
    else
      Q <= S and NQ after 5 ns;
      NQ <= R and Q after 5 ns;
  end if;
end procedure RS;

The return statement located in the if then clause causes the procedure to terminate when both S and R are equal to '1'. The procedure would terminate even if the end if would be followed by some other statements.

Example 2

P1: process
  type REAL_NEW is range 0.0 to 1000.0;
  variable a, b : REAL_NEW := 2.0;
  variable c: REAL;
  function Add (Oper_1, Oper_2: REAL_NEW) return REAL is
    variable result : REAL;
    begin
      result := REAL(Oper_1)+REAL(Oper_2);
      return result;
  end function Add;
begin
  c:= Add(a,b);
end process;

The return statement in a function must return a value of the type specified in the function header after the return clause.

Important Notes

  • Although the return statement is a sequential one, it is not allowed to use it in a process.

Wednesday, 19 December 2012

SystemVerilog Fork Disable

This is a long post with a lot of SystemVerilog code. The purpose of this entry is to hopefully save you from beating your head against the wall trying to figure out some of the subtleties of SystemVerilog processes (basically, threads). Subtleties like these are commonly referred to in the industry as "Gotchas" which makes them sound so playful and fun, but they really aren't either.

I encourage you to run these examples with your simulator (if you have access to one) so that a) you can see the results first hand and better internalize what's going on, and b) you can tell me in the comments if this code works fine for you and I'll know I should go complain to my simulator vendor.

OK, I'll start with a warm-up that everyone who writes any Verilog or SystemVerilog at all should be aware of, tasks are static by default. If you do this:

module top;
task do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork
do_stuff(10);
do_stuff(5);
join
end
endmodule

both do_stuff calls will wait for 5 time units, and you see this:


waited 5, then did stuff
waited 5, then did stuff

I suppose being static by default is a performance/memory-use optimization, but it's guaranteed to trip up programmers who started with different languages. The fix is to make the task "automatic" instead of static:


module top;
task automatic do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork
do_stuff(10);
do_stuff(5);
join
end
endmodule

And now you get what you expected:


waited 5, then did stuff
waited 10, then did stuff

That's fine, but that extra action from the slower do_stuff after the fork-join_any block has finished might not be what you wanted. You can name the fork block and disable it to take care of that, like so:


module top;
task automatic do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork : do_stuff_fork
do_stuff(10);
do_stuff(5);
join_any
$display("fork has been joined");
disable do_stuff_fork;
end
endmodule

Unless your simulator, like mine, "in the current release" will not disable sub-processes created by a fork-join_any statement. Bummer. It's OK, though, because SystemVerilog provides a disable fork statement that disables all active threads of a calling process (if that description doesn't already make you nervous, just wait). Simply do this:


module top;
task automatic do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork : do_stuff_fork
do_stuff(10);
do_stuff(5);
join_any
$display("fork has been joined");
disable fork;
end
endmodule

And you get:


waited 5, then did stuff
fork has been joined

Nothing wrong there. Now let's say you have a class that is monitoring a bus. Using a classes are cool because if you have two buses you can create two instances of your monitor class, one for each bus. We can expand our code example to approximate this scenario, like so:


class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask

task monitor();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable do_stuff_fork;
endtask
endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;
initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join
$display("main fork has been joined");
end
endmodule

Note that I went back to disabling the fork by name instead of using the disable fork statement. This is to illustrate another gotcha. That disable call will disable both instances of the fork, monitor 1's instance and monitor 2's. You get this output:


monitor 1 waited 6, then did stuff
monitor 1 fork has been joined
monitor 2 fork has been joined
main fork has been joined

Because disabling by name is such a blunt instrument, poor monitor 2 never got a chance. Now, if you turn the disable into a disable fork, like so:


class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask

task monitor();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
endtask

endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;
initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join
$display("main fork has been joined");
end
endmodule

You get what you expect:


monitor 1 waited 6, then did stuff
monitor 1 fork has been joined
monitor 2 waited 7, then did stuff
monitor 2 fork has been joined
main fork has been joined

It turns out that, like when you disable something by name, disable fork is a pretty blunt tool also. Remember my ominous parenthetical "just wait" above? Here it comes. Try adding another fork like this (look for the fork_something task call):


class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

function void fork_something();
fork
# 300 $display("monitor %0d: you'll never see this", id);
join_none
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask

task monitor();
fork_something();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
endtask

endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;

initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join
$display("main fork has been joined");
end
endmodule

The output you get is:


monitor 1 waited 6, then did stuff
monitor 1 fork has been joined
monitor 2 waited 7, then did stuff
monitor 2 fork has been joined
main fork has been joined

Yup, fork_something's fork got disabled too. How do you disable only the processes inside the fork you want? You have to wrap your fork-join_any inside of a fork-join, of course. That makes sure that there aren't any other peers or child processes for disable fork to hit. Here's the zoomed in view of that (UPDATE: added missing begin...end for outer fork):


task monitor();
fork_something();
fork begin
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
end
join
endtask

And now you get what you expect:


monitor 2 fork has been joined
monitor 1 fork has been joined
monitor 1 waited 6, then did stuff
monitor 2 waited 7, then did stuff
main fork has been joined
monitor 1 waited 11, then did stuff
monitor 2 waited 12, then did stuff
monitor 2: you'll never see this
monitor 1: you'll never see this

So, wrap your fork-join_any inside a fork-join or else it's, "Gotcha!!!" (I can almost picture the SystemVerilog language designers saying that out loud, with maniacal expressions on their faces).

But wait, I discovered something even weirder. Instead of making that wrapper fork, you can just move the fork_something() call after the disable fork call and then it doesn't get disabled (you actually see the "you'll never see this" message, try it). So, you might think, just reordering your fork and disable fork calls and that will fix your problem. It will, unless (I learned by sad experience) the monitor task is being repeatedly called inside a forever loop. Here's a simplification of the code that really inspired me to write this all up:

class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

function void fork_something();
fork
# 30 $display("monitor %0d: you'll never see this", id);
join_none
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask // do_stuff

task monitor_subtask();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
fork_something();
endtask

task monitor();
forever begin
monitor_subtask();
end
endtask

endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;

initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join_none
$display("main fork has been joined");
# 60 $finish;
end
endmodule

The fork inside the fork_something task will get disabled before it can do its job, even though it's after the disable fork statement. Gotcha!!!


My advice is to always wrap any disable fork calls inside a fork-join.









Get free daily email updates!



Follow us!


Functional Coverage Options features

Dear Readers,

Functional Coverage is very important in Test Bench Design. It gives us a confidence on covered items listed on verification plan/items. Usually the goal of verification engineer is to ensure that the design behaves correctly in its real environment.

Here I would like to share some of the important feature of System Verilog Functional Coverage which helps engineer during verification activity.

Coverage Options available in System Verilog through which you can specify additional information in the cover group using provided options

1. Cover Group Comment - 'option.comment'
You can add a comment in to coverage report to make them easier while analysing:

covergroup
CoverComment ;
option.comment = "Register Definition section 1.1";
coverpoint reg;
endgroup

In example, you could see the usage of 'option.comment' feature. This way you can make the coverage group easier for the analysis.

2. Per Instance Coverage - 'option.per_instance'

In your test bench, you might have instantiated coverage group multiple times. By default System Verilog collects all the coverage data from all the instances. You might have more than one generator and they might generate different streams of transaction. In this case you may want to see separate reports. Using this option you can keep track of coverage for each 88instance.

covergroup
CoverPerInstance ;
coverpoint tr.byte_cnt;
option.per_instance = 1;
endgroup

3. Threshold using - 'option.at_least'
This feature is useful when you don't have sufficient visibility in to the design to gather robust coverage. There might be the cases where you just have an information of number of cycles that are needed for the transfers to cover required errors to get generated/simulated for defined cover point. Here you could set the option.at_leaset. For example if we know that we need 10 cycles to cover this particular cover point, you could define option.at_leaset = 10.

4. Control on Empty bins - option.cross_num_print_missing = 1000

System verilog coverage report by default shows only the bins with samples. But usually as a verification engineer our job is to verify all cover point that are listed in verification plan.

covergroup
CoverCrossNumPrintMissing ;
ByteCnt : coverpoint tr.byte_cnt;
Length : coverpoint tr.length;
option.cross_num_print_missing = 1000;
endgroup

5. Coverage Goal - option.goal
In system verilog, coverage goal for a cover group or point is the level at which the group or point is considered fully covered.

covergroup
CoverGoal ;
coverpoint tr.length;
option.goal = 80;
endgroup

These are the few important coverage option features which are very useful in defining/coding System Verilog Functional Coverage.

Get free daily email updates!

Follow us!

Tuesday, 18 December 2012

Sensitivity List

Formal Definition

A list of signals a process is sensitive to.

Simplified Syntax

(signal_name, signal_name, . . .)

Description

The sensitivity list is a compact way of specifying the set of signals, events on which may resume a process. A sensitivity list is specified right after the keyword process (Example 1).

The sensitivity list is equivalent to the wait on statement, which is the last statement of the process statement section.

Only static signal names, for which reading is permitted, may appear in the sensitivity list of a process, i.e. no function calls are allowed in the list.

Examples

Example 1

DFF : process (CLK,RST)
begin
  if RST = '1'
    then Q <= '0';
    elsif (CLK'event) and (CLK = '1')
     then Q <= D;
  end if;
end process DFF;
-- DFF : process
-- begin
-- if RST = '1'
-- then Q <= '0';
-- elsif (CLK'event) and (CLK = '1')
-- then Q <= D;
-- end if;
-- wait on RST, CLK;
-- end process DFF;

Here, the process is sensitive to the RST and CLK signals, i.e. an event on any of these signals will cause the process to resume. This process is equivalent to the one described in the comment section.

Important Notes

· A process with a sensitivity list may not contain any explicit wait statements. Also, if such a process statement is a parent of a procedure, then that procedure may not contain a wait statement as well.

Scalar Type

Formal Definition

Scalar type is a type whose values have no elements. Scalar types consist of enumeration types, integer types, physical types, and floating point types. Enumeration types and integer types are called discrete types. Integer types, floating point types, and physical types are called numeric types. All scalar types are ordered; that is, all relational operators are predefined for their values.

Syntax:

scalar_type_definition ::= enumeration_type_definition

                        | integer_type_definition

                        | floating_type_definition

                        | physical_type_definition

Description

The scalar type values cannot contain any composite elements. All values in a specific scalar type are ordered. Due to this feature all relational operators are predefined for those types. Also, each value of a discrete or physical type has a position number.

All numeric types (i.e. integer, floating point and physical) can be specified with a range which constrains the set of possible values.

Please refer to respective topics for more information on different scalar types (enumeration type, integer type, floating point type, physical type).

Signal Assignment

Formal Definition

A signal assignment statement modifies the projected output waveforms contained in the drivers of one or more signals

Simplified Syntax

signal_name <= [delay_mechanism ] waveform ;

signal_name <= [delay_mechanism ] waveform1 when condition1 else

      [delay_mechanism ] waveform2 when condition2 else

       . . .

      [delay_mechanism ] waveformn;

with selection select

    signal_name <= [delay_mechanism ] waveform1 when choice1,

      [delay_mechanism ] waveform2 when choice2,

      . . .

      [delay_mechanism ] waveformn when others;

Description

Signal assignment statement can appear inside a process or directly in an architecture. Accordingly, sequential signal assignment statements and concurrent signal assignment statements can be distinguished. The latter can be divided into simple concurrent signal assignment, conditional signal assignment and selected signal assignment.

The target signal can be either a name (simple, selected, indexed, or slice) or an aggregate.

All signal assignments can be delayed. See delay for details.

Sequential signal assignment

If a sequential signal assignment appears inside a process, it takes effect when the process suspends. If there are more than one assignments to the same signal in a process before suspension, then only the last one is valid. Regardless of the number of assignments to a signal in a process, there is always only one driver for each signal in a process (Example 1).

If a signal is assigned a value in a process and the signal is on the sensitivity list of this process, then a change of the value of this signal may cause reactivation of the process (Example 2).

Concurrent signal assignment

The concurrent signal assignment statements can appear inside an architecture. Concurrent signal assignments are activated whenever any of the signals in the associated waveforms change their value. Activation of a concurrent signal assignment is independent from other statements in given architecture and is performed concurrently to other active statements (Example 3). If there are multiple assignments to the same signal then multiple drivers will be created for it. In such a case, the type of the signal must be of the resolved type (seeresolution function).

Conditional signal assignment

Conditional signal assignment is a form of a concurrent signal assignment and plays the same role in architecture as the if then else construct inside processes. A signal is assigned a waveform if the Boolean condition supported after the when keyword is met. Otherwise, the next condition after the else clause is checked, etc. Conditions may overlap.

A conditional signal assignment must end with an unconditional else expression (Example 4).

Selected signal assignment

Selected signal assignment is a concurrent equivalent of a sequential case construct. All choices for the expression must be included, unless the others clause is used as the last choice (Example 5). Ranges and selections can be used as the choice (Example 6). It is not allowed for choices to overlap.

Examples:

Example 1

signal A, B, C, X, Y, Z : integer;
process (A, B, C)
begin
  X <= A + 1;
  Y <= A * B;
  Z <= C - X;
  Y <= B;
end process;

When this process is executed, signal assignment statements are performed sequentially, but the second assignment (Y <= A * B) will never be executed because only the last assignment to Y will be activated. Moreover, in the assignment to Z only the previous value of X will be used as the A + 1 assignment will take place when the process suspends.

Example 2

signal A, B, C, X, Y, Z : integer;
process (A, B, C)
begin
  X <= A + 1;
  Y <= A * B;
  Z <= C - X;
  B <= Z * C;
end process;

When the process is activated by an event on the signal C this will cause change on the signal B inside a process, which will in turn reactivate the process because B is in its sensitivity list.

Example 3

architecture Concurrent of HalfAdder is
begin
  Sum <= A xor B;
  Carry <= A and B;
end architecture Concurrent;

The above architecture specifies a half adder. Whenever A or B changes its value, both signal assignments will be activated concurrently and new values will be assigned to Sum and Carry.

Example 4

architecture Conditional of TriStateBuffer is
begin
  BufOut <= BufIn when Enable = '1'
    else 'Z';
end architecture Conditional;

The architecture specifies a tri-state buffer. The buffer output BufOut will be assigned the value of buffer input BufIn only when the Enable input is active high. In all other cases the output will be assigned high impedance state.

Example 5

architecture Concurrent of UniversalGate is
begin
  with Command select
     DataOut <= InA and InB when "000",
                InA or InB when "001",
                InA nand InB when "010",
                InA nor InB when "011",
                InA xor InB when "100",
                InA xnor InB when "101",
                'Z' when others;
end architecture Concurrent;

Architecture of UniversalGate is specified with a selected signal assignment. Depending on the value of the Command signal, the DataOut signal will be assigned value resulting from the logical operation of two inputs. If none of the specified codes appears, the output is set to high impedance.

Example 6

with IntCommand select
  MuxOut <= InA when 0 | 1,
            InB when 2 to 5,
            InC when 6,
            InD when 7,
            'Z' when others;

A specialized multiplexer is defined here with a selected signal assignment. Note that both range and selections can be used as a choice.

Important Notes

· Signal assignment statements are generally synthesizeable but delays are usually ignored.

· Choices in selected signal assignment are separated by colons.

· All signal assignments can be labeled for improved readability.