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

Resolution Function

Formal Definition

A resolution function is a function that defines how the values of multiple sources of a given signal are to be resolved into a single value for that signal.

Simplified Syntax

function function_name (parameters) return type;

function function_name (parameters) return type is

  declarations

  begin

   sequential statements

  end function function_name;

Description

The resolution function allows multiple values to drive a single signal at the same time. This is particularly important for buses, which are connecting multiple sources of data.

The specification of a resolution function is the same as for ordinary functions with one requirement: the resolution function must be pure.

Resolution functions are associated with signals that require resolution by including the name of the resolution function in the declaration of signals or in the declaration of the signal subtype.

Examples

Example 1

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
                    );
TYPE std_ulogic_vector IS ARRAY ( NATURAL RANGE <> ) OF std_ulogic;
FUNCTION resolved ( s : std_ulogic_vector ) RETURN std_ulogic;
SUBTYPE std_logic IS resolved std_ulogic;
TYPE std_logic_vector IS ARRAY ( NATURAL RANGE <>) OF std_logic;
TYPE stdlogic_table IS ARRAY(std_ulogic, std_ulogic) OF std_ulogic;
CONSTANT resolution_table : stdlogic_table := (
--   ---------------------------------------------------------
--   |  U    X    0    1    Z    W    L    H    -        |   |
--   ---------------------------------------------------------
     ( 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U' ), -- | U |
     ( 'U', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X' ), -- | X |
     ( 'U', 'X', '0', 'X', '0', '0', '0', '0', 'X' ), -- | 0 |
     ( 'U', 'X', 'X', '1', '1', '1', '1', '1', 'X' ), -- | 1 |
     ( 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', 'X' ), -- | Z |
     ( 'U', 'X', '0', '1', 'W', 'W', 'W', 'W', 'X' ), -- | W |
     ( 'U', 'X', '0', '1', 'L', 'W', 'L', 'W', 'X' ), -- | L |
     ( 'U', 'X', '0', '1', 'H', 'W', 'W', 'H', 'X' ), -- | H |
     ( 'U', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X' )  -- | - |
  );
FUNCTION resolved ( s : std_ulogic_vector ) RETURN std_ulogic IS
  VARIABLE result : std_ulogic := 'Z'; -- weakest state default
BEGIN
-- the test for a single driver is essential otherwise the
-- loop would return 'X' for a single driver of '-' and that
-- would conflict with the value of a single driver unresolved
-- signal.
  IF (s'LENGTH = 1) THEN RETURN s(s'LOW);
    ELSE
      FOR i IN s'RANGE LOOP
        result := resolution_table(result, s(i));
      END LOOP;
  END IF;
  RETURN result;
END resolved;

The example is a part of the Std_Logic_1164 Package specification. The name of the resolution function called Resolved is included into the declaration of the subtype Std_Logic (highlighted by boldface). The resolution function itself is declared at the end of the example.

Important Notes

  • Standard types (BIT and BIT_VECTOR) are not resolved and it is not possible to specify multiple-source buses with these types. This is quite restrictive for typical applications, which use buses.

  • Because Std_Logic and Std_Logic_Vector are resolved and can handle buses, they became the de facto industrial standard types.

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