13

VHDL'93 - new features

prepared by P. Bakowski


This topic is in preparation.


The VHDL development of the new generation of the language was guided by the following requirements:

The developers were oblidged to preserve the determinism. the enerality and the language scope(from system to gate level). The newly introduced mechanisms should be implemented with a minimal impact on the efficiency. No specific packages should be added.

If compared with VHDL'87 , VHDL'93 integrates several new elements:

In addition to these important features VHDL'93 provides new character set, extended identifiers and aliasing, etc.


Timing : postponed processes and rejection limit

To allow steady state modeling VHDL'93 introduces the mechanism of postponed process. Postponed process is executed at the end of a time step.

Note that in VHDL'87 there is no distinction between a time-in-seconds and a delta-time. The only way to get rid of delta glitches is to introduce minimum physical delay : wait for 1fs.

With VHDL'93 this can be done through postponed process:

or through postponed assert:

With VHDL'87 two delay models were defined: inertial and transport delay. In order to have more control over timing specifications VHDL'93 introduces a new delay model : inertial with pulse rejection window.

The rejection limit allows to model the glitches. For example a combination circuit has a delay of 10 ns but show a glitch if the input changes more than 6 ns later. This situation can not be modeled in VHDL'87.


Instancing and Incremental Binding

The instancing of an entity in a larger structural architecture required in VHDL'87 an explicit declaration of corresponding component.

This constaraint, in some cases cumbersome, is not mandatory in VHDL'93. In VHDL'93 the direct instancing of entities is allowed:

The incremental binding is a new feauture that allows to use configuration declarations to change bindings that were specified in configuration specifications. This feature provides more flexibility and helps the top-design process.

Example:

configuration specification:

configuration declaration for this architecture:


Shared variables and Monitors

A need to model the digital systems at higher abstraction levels (system level) requires to share data between processes . The shared variables are a modeling convenience in which the shared variables are not part of the physical hardware. They allow to build the models where the entities communicate via external objects such as queues and stacks. Shared variable also allow to model stochastic processes where the hardware operates in an environment containing pseudo-random number generators exploitet representing hardware load.


Shared variables can be declared outside of processes and sub-programs and can be used globally.

Example:


The use of shared variables creates two potential problems:

For example if two processes use the same shared variable shvar :

What is the resulting value of shvar after the execution of the above assignment ? It depends on the order of execution which can not be determined by the model itself.


These problems require specific mechanisms to assure atomicity of operations on shared data including exclusive assess.

Exclusif access

The first solution enabling atomic access is the use of access flags.

Example:

The execution of the above processes may lead however to an inconsistent result:

Conclusion:

The nondeterminism of the process activation may generate an inconsistent result !


The above examples show that simple declaration of shared variables is unsufficient to provide atomicity of the operations and consistency of the results.

That is why the working group (SVWG) decided to introduce monitor mechanism to protect the access to shared variables.

The monitors are used to protect the access not only to shared variables (variables) but to a number of language constructs such as functions and procedures. The potected type has been introduced.

The protected type definition may contain declarative items in any order provided that declaration precedes use (as elsewhere in VHDL, consistent with existing visibility rules):

        protected_type_definition_declarative_part ::=
                { protected_type_body_declarative_item }
                
        protected_type_definition_declarative_item ::=
                     subprogram_declaration
                |    subprogram_body
                |    type_declaration
                |    subtype_declaration
                |    constant_declaration
                |    variable_declaration
                |    file_declaration
                |    alias_declaration
                |    use_clause
                |    attribute_declaration
                |    attribute_specification
                |    group_template_declaration
                |    group_declaration



For mor details see: shared variable - language change specification (SVWG)

The declarations which can appear in a protected type definition are identical to those which can appear in a subprogram body.


Examples : (these examples are taken from the SVWG document)

for more details see: shared variable - language change specification - (SVWG)

In order to illustrate the protected approach defined in this language change specification, it is useful to consider four examples of increasing complexity: a shared counter protected type, a complex number protected type, a variable size array protected type and a mutual-exclusion semaphore. Each of these examples illustrates a different characteristic of this protected type design.

Shared Counter

The shared counter example illustrates an integer that must be atomically incriminated, decremented and observed. Note that all three of these operations are monadic in nature.

First the shared counter protected type specification and body appear:

       type SharedCounter  is protected
                procedure increment (n: integer);
                procedure decrement (n: integer);
                function value return integer;
        end protected SharedCounter;
        ....
        type SharedCounter is protected body

                variable counter_value: integer := 0;

                procedure increment (n: integer) is
                        begin
                                counter_value := counter_value + n;
                        end procedure increment;
                procedure decrement (n: integer) is
                        begin
                                counter_value:= counter_value - n;
                        end procedure decrement;
                function value return integer is
                        begin
                                return counter_value;
                        end procedure value;

        end protected body SharedCounter;

Then a shared variable is created using the SharedCounter protected type:

        shared variable counter : SharedCounter;

Finally, several processes may utilize the shared variable, such as the example process below:

        example_process:
                process is
                        ...
                        counter.increment(5);
                        ...
                        counter.decrement(i);
                        ...
                        v := counter.value;

                        if (v = 0) then
                                ...-- by the time execution gets to here, the counter may
                                ...-- have changed value!
                        end if;
                end process example_process;

It is important to note that the if condition only insures that the counter was 0 at the instant of the call; by the time comparison occurs, the counter may have increased or decreased in value.

If it is important to insure that the semaphore remains zero while the comparison and if condition executes, a more complex CountingSemaphore would be required with methods to conditionally lock and unlock the counting semaphore. The caller (example_process in this case) would try to acquire the explicitly programmed lock. If the CountingSemaphore's lock subprogram granted access via that call, it would return a key by which the owner would be known in a subsequent call and the semaphore would reject any other lock request. The semaphore would only honor increment and decrement requests which contained the appropriate key (belonging to the exclusive owner). When the conditional operation was done, the owner of exclusive access must unlock the semaphore. While illustrating that a semaphore can be embedded in a monitor with simple monadic functionality, the responsibility is on the human to insure that every lock is matched by an unlock on every path.

Abstracting high-level function into the protected type can simplify the call down to a single call to the object of protected type. For example, the call to get the semaphore's value followed by a comparison with zero and an assert might be abstracted into a single CountingSemaphore subprogram which triggers a VHDL assert statement if the semaphore_value is zero.


Complex Number

The complex number example illustrates a protected type which could be represented as a real and imaginary part or a phase and magnitude. For purposes of illustration, the example happens to use a real and imaginary representation, although this should not be discernible from the interface. A single operator, addition, serves to illustrate definition of a dyadic operator for the complex number type.

       type ComplexNumber is protected
                procedure extract (variable r, i: out real);
                procedure add (variable a, b: in ComplexNumber);
        end protected ComplexNumber;
        ...
        type ComplexNumber is protected body

                variable re, im: real;

                procedure extract (variable r, i: out real) is
                        begin
                                r := re;
                                i := im;
                        end procedure explode;
                procedure add (variable a, b: in ComplexNumber) is
                                variable a_real, b_real : real;
                                variable a_imag, b_imag : real;
                        begin
                                a.extract(a_real, a_imag);
                                b.extract(b_real, b_imag);
                                re := a_real + b_real;
                                im := a_imag + b_imag;
                        end procedure add;


        end protected body ComplexNumber;

Then in some concurrent declarative region, perhaps that of an architecture, three shared variables are declared with default initial values.

        shared variable sv1, sv2, result : ComplexNumber;

Sequential statements and concurrent procedure calls may reference these shared variables from many different processes.


Variable Size Array

The variable size array example illustrates a protected type capable of representing an object of varying size. In this case, the varying size refers to the number of bit elements stored:

 type VariableSizeBitArray is protected
                procedure add_bit (index: positive; value: bit);
                function size return integer;
       end protected VariableSizeBitArray;
  type VariableSizeBitArray is protected body
                type bit_vector_access is access bit_vector;

                -- In this simple case, element initalization works, however note
                -- the alternative approach suggested by Peter Ashenden below...
                variable bit_array: bit_vector_access := NULL;
                variable bit_array_length : natural := 0;
                procedure add_bit (index: positive; value : bit) is
                                variable tmp : bit_vector_access;
                        begin
                                if  index > bit_array_length then
                                        tmp := bit_array;
                                        bit_array_length := index;
                                        bit_array := new bit_vector(1 to index);
                                        if tmp /= null then
                                                bit_array(1 to bit_array_length) := tmp.all;
                                                deallocate (tmp);
                                        end if;
                                end if;
                                bit_array (index) := value;
                        end procedure add_bit;

                function size return integer is
                        begin
                                return bit_array_length;
                        end function size;
                impure function initialize return boolean is
                        begin
                                bit_array := null;
                                bit_array_length := 0;
                                return true;
                        end function initialize;
          constant initialized : boolean := initialize;

        end protected body VariableSizeBitArray;

Within a sequential declarative region, perhaps a process, we can then declare a variable bit_stack variable bit_stack: VariableSizeBitArray;

The sequential code within the process may then initialize the bit_stack then adds three elements:

        bit_stack.add_bit(1,'1');
        bit_stack.add_bit(2,'1');
        bit_stack.add_bit(3,'0');

The VariableSizeBitArray protected type could equally well have been used to create a shared variable accessible from several processes during the same delta cycle. As long as the array was initialized once, the bit_stack should function equally well.

The interested reader is urged to sketch other short examples which illustrate use of protected types for scenarios of interest to the reader. Authors of this LCS would be very interested in examples which imply incorrect results under some parallel processing scenerios or which seem unacceptably awkward.


Semaphore

Monitors are intended to provide a more powerful, higher level mechanism than semaphores, however monitors do not readily replace semaphores. Chuck Swart prepared this example to illustrate the complexity of implementing a semaphore using monitors.

In general, semaphores are used in the following way:

        p1: process is
                ...
                P(s); -- Block until semaphore s is acquired
                ... -- Critical section c1
                V(s); -- Release semaphore s
                ...
        p2: process is
                ...
                P(s); -- Block until semaphore s is acquired
                ... -- Critical section c2
                V(s); -- Release semaphore s;
                ...

The P and V procedures are executed atomically. When a process executes a P the process requests control of the semaphore. If another process controls the semaphore, execution of the requesting process is blocked until the semaphore is available. Execution of V releases the semaphore for use by other processes.

The above code uses semaphores to guarantee that critical regions c1 and c2 will not execute simultaneously.

Here is an example which tries to emulate the above code using protected types:

Unless this code executes in an environment which interleaves execution of p1 and p2, the code will not execute as expected. However, most, if not all, current uniprocessor implementations execute a single process until that process executes a wait statement. Under this common implementation, if p1 executes while p2 is in critical section c2, then process p1 will busy wait forever, since control will never be given to p2 and, thus, the semaphore will never be released.

The preferred solution is to reformulate the VHDL code as a monitor. The critical sections (C1 and C2 above) are rewritten as two procedures in a protected type declaration (and body). In this case the protected type body need not have any state (variable declarations). An object of protected type CriticalSection can be created and referenced from two or more processes.

        type CriticalSection is protected
                procedure c1;
                procedure c2;
        end protected CriticalSection;
        ...
        type CriticalSection is protected body

                procedure c1 is
                        begin
                                -- Body of critical section c1
                        end procedure c2;
                procedure c2 is
                        begin
                                -- Body of critical section c2
                        end procedure c2;
                
        end protected body CriticalSection;

        shared variable Semaphore : CriticalSection;


Pure and Impure functions

In VHDL'93 there are two kinds of functions : memoryless functions referred to as pure functions and functions with memory refered to as impure functions. A pure function value depends only on the input parameters. Note that VHDL'87 provides only memoryless functions.

One of the obvious application of impure functions is random numbers generation. The pseudo-random sequences are based on a seed value which is updated at each call, and used to calculate the next pseudo-random value. Without persistent memory it is impossible to develop a function generating pseudo-random values.


Files and Text I/O

For the VHDL'93 file primitives such as FILE_OPEN and FILE_CLOSE, the mode of file parameter is inout. This solution allows easier interprocess communication.

The text I/O package has no ENDLINE indicator. The standard input/output can be opened or closed by OPEN and CLOSE primitives.

An attribut F'length allows to know the position in the file; f'length=0 means end_of_file.


New attributes

In order to convert the strings into values and values into strings,two new attributes representing the string image ('image) and value and ('value) have been added.

Note: VHDL'87 has already an attribut val

T'val(X) returns a value (of base T) whose position is the universal integer value X.


'path_name and 'instance_name are new attributes provided to identify the hierarchy of entities.

The 'instance_name atribute reflects the instance name :

The 'path_name atribute informs about the instanced entity and architecture:

These attributes offer easier debugging and provide the mechanism for backannotation.


Backannotation - an example

The backannotation is required when we need to update the delay values after the synthesis process.

The backannotation was a hard task for the users of VHDL'87. With the new features such as shared variables, impure functions, 'instance_name and 'value attributes introduced into VHDL'93 the backannotation may be prepared in the source desrcription.

Assume tha annotated value file is constructed such that for each place in the hierarchy a delay value is assigned:

At a model start up and impure function can read the file and hash the values (based on the hierarchy name) to a global hash table using shared variables:

The function read_and_hash computes an integer tag and logs a new entry in the table using the name, and delay from the file.

From each instance another impure function named update_delay is called to get the correct delay value from the table for that instance. This is done at the simulation initialization.

The function update_delay computes a tag from the name, looks it up in the global table and returns the appropriate delay.

To annotate non-numeric values, it is possible to use text files. Such files may include strings, which are converted to enumerations using attributes

Assume a file has:


Groups

The group construct allows for the named association of other objects and names. It is useful for synthesis or for evaluation.

Group has no simulation semantics.


Foreign units and subprograms

The attribute foreign allows the denotation of subprograms and architectures as foreign - implemented outside of VHDL.


New operators

VHDL'93 integrates predefined shift operators:

xnor is added to complete the logic operators


'driving and 'driving_value

The 'driving attribute permits to know if a signal is driven from within a process at a particular time.

This allows to localize the source of the event.

The 'driving_value attribute allows to test the value of the local driver of a signal:


Signature and Aliasing

Extended character set and identifiers

'ascending


Changes:

sensitivity list

report statement

concatenation

staticness


Implementation aspects



Exercises