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

Record Type

Formal Definition

A composite type whose values consist of named elements.

Simplified Syntax

type record_type_name is record

    element_name : element type;

    element_name : element type;

    . . .

    end record record_type_name;

Description

The record type allows declaring composite objects whose elements can be of different types. This is the main difference from arrays, which must have all elements of the same type. All elements are declared with individual names together with subtype indication. If two or more elements are of the same subtype they can be declared together (Example 1). The names of elements in each record must be distinct. The same element name, however, can be used in different records.

The value of an object of type record is a composite value, consisting of the values of its elements. The assignment of a value to an object of the type record can be realized either through an aggregate or through individual assignments to elements (selected names).

Aggregate-based assignment to a record, either positional or named association, can be used (Example 2). If the positional association is used, it is assumed that the elements are listed in the order defined in the record declaration. If the others choice is used, it must represent at least one element. If there are two or more elements assigned by the otherschoice, then all these elements have to be of the same type.

When individual assignment to elements are used then each element id referenced by the record object name followed by a dot and element's name (example 3).

Expression assigned to an element of a record must result in a value of the same type as the element.

Examples

Example 1

type RegName is (AX, BX, CX, DX);
type Operation is record
    Mnemonic : String (1 to 10);
    OpCode : Bit_Vector(3 downto 0);
    Op1, Op2, Res : RegName;
end record;

The record type defined above represents an information from an instruction list of a processor. There are five elements here: mnemonic code (a string), operation code (four bit), two operands and the destination. Note that the last three elements are declared together as they are of the same type.

Example 2

-- type declarations are given in Example 1
variable Instr1, Instr2: Operation;
. . .
Instr1:= ("ADD AX, BX", "0001", AX, BX, AX);
Instr2:= ("ADD AX, BX", "0010", others => BX);

Here, the two assignments to variables of the record type (Operation) are performed with aggregates. Note the way the choice others was used in the second example.

Example 3

-- type declarations are given in Example 1
variable Instr3 : Operation;
. . .
Instr3.Mnemonic := "MUL AX, BX";
Instr3.Op1 := AX;

In this case direct assignments to individual elements of a record object are performed. Note the way an element is referenced: record name, dot, and element name.

Important Notes

· Linear records (i.e. record, where elements are of not of composite type) are generally synthesizable.

· Files are not allowed as elements of records.

Report Statement

Formal Definition

A statement that displays a message.

Simplified Syntax

report string;

report string severity severity_level;

Description

The report statement is very much similar to assertion statement. The main difference is that the message is displayed unconditionally. Its main purpose is to help in the debugging process.

The expression specified in the report clause must be of predefined type STRING, and it is a message that will be reported when the assertion violation occurred.

If the severity clause is present, it must specify an expression of predefined type SEVERITY_LEVEL, which determines the severity level of the assertion violation. The SEVERITY_LEVEL type is specified in the STANDARD package and contains following values: NOTE, WARNING, ERROR, and FAILURE. If the severity clause is omitted in a report statement it is implicitly assumed to be NOTE (unlike in an assertion statement, where the default is ERROR).

Examples

Example 1

while counter <= 100 loop
  if counter > 50
    then report "the counter is over 50";
  end if;
  . . .
end loop;

Whatever happens inside the loop, if the value of counter is greater than 50 it will be reported by the listed message. The severity clause is omitted here because the selected level is the same as the default one (NOTE).

Important Notes

  • The report statement was introduced as late as in VHDL 93 and is equivalent to the assert false statement. The latter form was the only acceptable in VHDL 87.

Reserved KeyWord

Definition:

The reserved word is an identifier reserved in the VHDL language for a special purpose.

Description

The reserved words cannot be used as explicitly declared identifiers. The complete list of reserved words is given below:

abs
after
alias
all
and
architecture
array
assert
attribute

begin
block
body
buffer
bus

case
component
configuration
constant

disconnect
downto

else
elsif
end
entity
exit

file
for
function

generate
generic
group
guarded

if
impure
in
inertial
inout
is

label
library
linkage
literal
loop

map
mod

nand
new
next
nor
not
null

of
on
open
or
others
out

package
port
postponed
procedure
process
pure

range
record
register
reject
rem
report
return
rol
ror

select
severity
signal
shared
sla
sll
sra
srl
subtype

then
to
transport
type

unaffected
units
until
use

variable

wait
when
while
with

xnor
xor

Important Notes

· VHDL is case insensitive, therefore there is no difference using either uppercase or lowercase for reserved words.

· If an identifier is placed between leading and trailing backslashes, it becomes an extended identifier and is no longer a reserved word (e.g. \port\ is not a reserved word).

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!