1

VHDL basic programming elements

prepared by P.Bakowski


Introduction

VHDL has some deep roots in ADA language. Those familiar with  ADA  will notice several similarities especially concerning the language lexical elements , its syntax and the constructs used to describe the algorithms.
The advantage to know the syntax and the programming concepts  of ADA is that you do not need to learn the same programming-oriented elements present in VHDL. The drawback is that  ADA is not a simulation language and many hardware oriented features of  VHDL have specific  interpretation.

This chapter starts with the presentation of  language lexical elements allowing to build  identifiers. The identifiers are the names of objects such as constants, variables or signals and the names of literals.

The objects may be seen as forms or containers, the literals are contentsof objects. Each object is characterized by its type or form. Some objects are simple, some are complex or composed. The content of variables and signals vary during the simulation process. The content of constants is always the same.

We will present elementary operators that can be applied to basic objects. We will illustrate how to build arithmetical and logical expressions from basic objects and operators. The operators must correspond to the nature and to the type of the involved objects.

There are three classes of basic objects in VHDL: the variables, the constants and the signals. The variables and constants are well known programming objects. The signals are hardware oriented; they evolve along the time flow. Given their great importance and specific meaning, the signals will be studied later in a separate chapter..

Notational conventions:

The text is written with variable width policy; some important parts of the text are written with bold characters.
VHDL code is written with fixed width characters. The keywords are written with fixed width blue color characters. Some important elements in VHDL code may be signaled by using red color warning. Note that the types of the objects are not treated as VHDL keywords.


Contents: identifiers, bit strings, data types, enumeration types, arrays, physical types, records, subtypes, object declarations, alias, attributes, operators and expressions, access type , exercises
 


Lexical Elements

The lexical elements (lexems) are basic language elements.  They are used to compose the names and words, to express the operators and delimiters.

Comments

The comments have no meaning in language description. In VHDL they start with two adjacent hyphens (--) at the beginning of the line.
-- this is not VHDL code
Note: Some front-end systems like synthesizers use the comments to provide the directives.
-- minimum delay (it is a directive)

Identifiers

There are two kinds of identifiers: The first character of the identifier must be a letter.

Identifier567, Toto, Alu_in, Cin, Ir4, Address_bus, ..

Note that similar like in the ADA language the case of letters is not considered significant, so the identifiers ireste and IRESTE are the same. The use of underline character generates different identifiers, so May_21 and May21 are different identifiers.

see also delimiters

Characters and character strings

Characters are formed by enclosing an ASCII character in single quote marks.
`b' ,`&' ,` `
Strings of characters are formed by enclosing the characters in double quote marks. To include a double quote mark itself in a string, a pair of double quote marks must be put together. A string can be used as a value for an object which is an array of characters.
"A string" "" -- empty string " ""A string""-- a string in a string

Numbers

If  number includes a point, it is a real number, otherwise it represents an integer.
128 4_56_7 897E4 -- integer numbers
0.5 5.5 3.14 1.8_7 33.3E-3 -- real numbers

Based numbers

Based numbers are defined by the base value followed by a # character and the based integer ; they terminate by another # character. The letters A to F(upper or lower case) are used to code the 10 to 15 hexadecimal numbers.
2#1010# 2#1111_0000# or B"1111_0000"
16#AF# or X"AF"
8#65# or O"65'
4#301#

Bit Strings

VHDL provides a convenient way of specifying literal values for arrays of type bit ( `0's and `1's,)

Base  B stands for binary, O for octal and X for hexadecimal.

B"10111011" -- length is 8
X"774" -- length is 12, equivalent to B"0111_0111_0100" 


Data Types

VHDL defines provides several basic scalar types and mechanisms to create  composite types. Basic VHDL types are defined in the standard package
The scalar types are: The composite types are arrays and records. VHDL provides access types allowing the dynamic use of variables. File objects are also available to perform input/output communication with user environment

Integer Types

An integer type is a subset of values considered as a range of integer values. The ascending ranges are declared with the keyword: to, the descending range with the keyword : downto.
 
type signed_int is range -32768 to 32767;
type unsigned_int is range 0 to 65356;
type octet is range 0 to 255; type entry is range 31 downto 0;

 

The range of integer is implementation defined, though it is guaranteed to cover - 2147483647 to +2147483647.

Enumeration Types

An enumeration type is a parenthesized list of enumeration literals.  Each literal may be an identifier or a character literal.
 
type four_level_logic is ( 'X','0', '1', 'Z')
type seq_state is (ready, active, wait)
Several predefined enumeration types are defined as follows in the standard package:
type character is ( NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, HT, LF, VT, FF, CR, SO, SI, DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FSP, GSP, RSP, USP, ` `, `!', `"', `#', `$', `%', `&', `'', `\ `, `*', `+', `,', `-', `.', `/', `0', `1', `2', `3', `4', `5', `6', `7', `8', `9', `:', `;', `<`, `=', `>', `?', `@', `A', `B', `C', `D', `E', `F', `G', `H', `I', `J', `K', `L', `M', `N', `O', `P', `Q', `R', `S', `T', `U', `V', `W', `X', `Y', `Z', `[`, `\\', `]', `^', `_', ``', `a', `b', `c', `d', `e', `f', `g', `h', `i', `j', `k', `l', `m', `n', `o', `p', `q', `r', `s', `t', `u', `v', `w', `x', `y', `z', `{`, `|', `}', `~', DEL)
type severity_level is ( note, warning, error, failure)
type boolean is ( false, true)
type bit is ( `0', `1')

Physical Types

A physical type is a numeric type used to represent some physical quantity, such as resistance, length, time or voltage. The physical type definition specifies a range constraint, one base unit, and secondary units. Secondary units are   multiples of the base unit.  The most important of the predefined physical types is time:
 
type time is range implementation_defined
units
fs;
ps = 1000 fs;
ns = 1000 ps;
us = 1000 ns;
ms = 1000 us;
sec = 1000 ms;
min = 60 sec;
hr = 60 min;
end units ;
...
...
type resistance is range 1 to 10E12
units
ohm;
kohm=1000 ohm;
mohm= 1000 kohm
end units ;

Real Type

Range of real type is implementation-dependent, it is defined in the package standard. It must cover at least the range from  -1E38 to +1E38.  The real types/subtypes  may be defined as follows:
 
type signal_level is range -60.00 to +60.00;
type probability is range 0.0 to 1.0;
subtype real2to4 is real range 2.0 to 4.0;

Subtypes

The use of a subtype allows to restrict the range of some base type. This is especially important for the synthetizable descriptions using integer types. The restrictions may concern the range of a scalar type or the size of a composite type.
Scalar types:
 
subtype natural is integer range 0 to integer'high;
subtype positive is integer range 1 to integer'high;
subtype counter is integer range 0 to 255;
subtype pin_count is integer range 0 to 400;
subtype digits is character range `0' to `9';

Composite types:
 

subtype id is string (1 to 20);
subtype word is bit_vector (31 downto 0);

 

 


Arrays and records

Arrays are homogeneous composite types; they are seen as a collection of elements all of the same type. Arrays may be one-dimensional with one set if indices or  multi-dimensional with a number of indices. An individual element of the array is referenced with an array identifier, followed by an index value(s) placed in parentheses. Records types contain several elements, each element having  different name and possibly different type.
reg(1)  -- array name with one index 1
mem(1,1) -- bidimensional array name with two indices
The  constrained array is an array whose size is determined (e.g. 4 elements).  An  unconstrained array defines the type of elements, but not its size.
 
type mot is array (31 downto 0) of bit;
type counter is array (0 to 31) of integer;
type screen  is array (column_range, row_range) of pixel;

An example of an unconstrained array type declaration:
 

type b_vector is array (integer range <>) of bit;
type screen  is array (integer range <>, integer range <>) of pixel;

Determination of the array size represented by symbol <> is deferred until an array is declared to belong to this unconstrained type. There are two predefined array types, both of which are unconstrained. They are defined as:
 

type string is array (positive range <>) of character; t
type bit_vector is array (natural range <>) of bit;

The types positive and natural are subtypes of integer defined in the standard package: .

A contiguous slice of a one-dimensional array can be referred to by using a range as an index. For example reg(16 to 31) is an sixteen element array which is part of the array reg.

Aggregates and string literals

Values of arrays can be given in array aggregate notation. An aggregate is a list of elements  specifying the values for elements in an array.
Suppose we have an array type declared as:
 
type lookup is array (3 downto 0) of integer range 0 to 9;

In order to initialize the array we can use a  positional association as follows:
 

(4,5,7,8) -- positional association aggregate

In this case  the elements are listed in the order of the index range, starting with the left bound of the range. We can use also a named association where the index for each element is explicitly given:
 

( 3 => 8, 2 => 7, 0 => 4, 1 => 5) -- named association aggregate

Note that  the elements can be written in any order.
 The keyword others can be used to indicate the default value to be used for all elements that are not explicitly listed:
 

( 3 => 1, 2 => 1, 0 => 4, 1 => 1) or ( 0 => 4, others => 1)

The following is a complete example of a memory block declaration and initialization using others

type t_x01 is ('X','0','1');
type mot is array(31 downto 0) of t_x01;
type memoire is array (0 to 1023) of mot;
variable RAM:memoire:=(others =>(others =>'0'));

Records

Records types contain several elements, each element having  different name and possibly different type.
 
type instruction is record
op_code : code;
address_code : mode;
operand1, operand2: address;
end record ;
...
type message is record
mess_type: integer;
mess_size: integer;
mess_text: data_type:
end record;

In order to refer to an element of record we use the name of the record followed by the element name.

message.mess_size
message.mess_type

Aggregates can be used to initialize the records.
 

type message is record
mess_type: integer;
mess_size: integer;
mess_text: data_type:
end record;

variable my_mess: message;

my_message := (2,4,"toto");   -- an aggregate to initialize message record
my_message := (0 => 2,2 => "toto", 1 => 4);   -- named association


Constants and Variables

Constants and variables are two of three main classes of objects used in VHDL. An object belongs to a class: Each object has a type. While the constants and variables are typical programming objects, the signals are specific to structural descriptions involving timing aspects.  That is why the VHDL signals are explained in a separate chapter dedicated to signals.

Constants

A constant is initialized to a specified value when it is created. This value cannot be modified. If the assignment symbol (:=) followed by a value is not present in a constant declaration, then the declaration declares a  deferred constant.  Such a declaration may only appear  in package  body.
 
constant : PI : real := 3.141;
constant period : time := 5 ns;
constant max_size : natural;  -- deferred constant
..

Arrays of constants

..
type hexadecimal is ('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
type hexa_index is array(hexadecimal) of boolean;
constant decimal: hexa_index:=('0' to '9' => true, 'a' to 'f' => false);
..

Variables

Variables are objects used to contain the changing values.  A variable may be initialized when it is created. A default value is assigned when the variable is not explicitly initialized. This default value for scalar types is the leftmost value for the type. Depending on the type it can be the value of  the first enumerated element , the lowest value in an ascending range, or the highest value in a descending range.
 
variable instruction : bit_vector(31 downto 0);
variable flag : bit:= '1';

For a variable of composite type, the default value is the composition of the default values for each element.


Alias

An alias declares an alternate name for an existing object. This allows to enhance the readability of the object depending on the context. A reference to an alias is interpreted as a reference to the object or the part of the object specified in alias declaration.

variable instruction : bit_vector(31 downto 0);

alias op_code : bit_vector(7 downto 0) is instruction(31 downto 24);
alias source_oper1 : bit_vector(3 downto 0) is instruction(23 downto 20);
alias source_oper2 : bit_vector(3 downto 0) is instruction(19 downto 16); ...
...
variable reel: bit_vector(31 downto 0); alias signe: bit is real(31);
alias exposant: bit_vector(6 downto 0) is real(30 downto 24);
alias mantissa: bit_vector(23 downto 0) is real(23 downto 0);


Attributes

VHDL objects carry an additional information called attributes.  There are user defined attributes discussed later and standard predefined attributes.
There are five categories of predefined attributes depending on the returned result: An object attribute is referenced using the ' notation. (tick notation) For example, object'attr refers to the attribute attr of the  object object  .

Scalar based attributes :

Given a scalar type or subtype T, then the following attributes can be used:
 
 
attribute name prefix result
T'base type/subtype base type of T.; must be prefix to another attribute ('high, 'low,..)
T'left  

T'right

scalar type the left bound of T, result of type T  

the right bound of T, result of type T

T'high 

T'low

scalar type the upper bound of T, result of type T  

the lower bound of T, result of type T

T'ascending  

(VHDL'93)

scalar type TRUE if type is ascending, otherwise FALSE
subtype INDEX is integer range 0 to 63
variable T: INDEX;
INDEX'base'high gives 63
INDEX'left gives 0
INDEX'right gives 63
INDEX'low gives 0
INDEX'high gives 63
 

Discrete attributes

Given a discrete or physical type or subtype T, where X is a member of T, the following attributes can be used:
 
 
attribute name prefix result
T'pos(X)  

T'val(X)

discrete returns a universal integer indicating the position number of parameter X of type T; first position is 0  

returns the value (of base T) whose position is the universal integer value X

T'succ(X)  

T'pred(X)

discrete returns a value of type T whose value is the position number one greater than the one of the parameter.  

returns a value of type T whose value is the position number one less than the one of the parameter.

T'leftof(X)  

T'rightof(X)

discrete returns the value that is to the left of parameter of type T  

returns the value that is to the right of parameter of type T

type COULEUR is (White,Red,Green,Black);
variable T: COULEUR;
COULEUR'pos(Red) gives 1
COULEUR'val(3) gives Black
COULEUR'succ(Red) gives Green
COULEUR'prec(Black) gives Green
COULEUR'leftof(Red) gives White
COULEUR'rightof(Green) gives Black
 

Array based attributes

For an array type or object A, with one or more dimensions ( N) , the following attributes can be used:
 
 
attribute name prefix result
A'left(N)  

A'right(N)

array returns the left bound of the Nth index range of A; N is of type universal integer; result type is of type of the left bound of the left index range; N=1 if omitted  

the same as A'left(N) except that the right bound is returned

A'high(N)  

A'low(N)

array returns the upper bound of the range of A; result type is the type of the Nth index range of A; N=1 if omitted  

the same as A'high(N) except that the low bound is returned

A'range(N)  

A'reverse_range(N)

array returns range of A'left(N) to A'right(N)  

returns range of A'ight(N) to A'left(N)

subtype BV32_Up is bit_vector(0 to 63);
subtype BV32_Down is bit_vector(31 downto 0);
type MEM16_Typ is array(0 to 15) of BV32_Up;
variable A: BV32_Up;
variable B: BV32_Down;
variable M: MEM16_Up;
A'left gives 0
B'left gives 31
A'right gives 63
M'left(2) gives 0
A'high gives 63
B'high gives 31


Operators

The basic objects and literals may be composed into expressions combining them with operators. In an expression the operators are executed following their precedence order. Operators are listed in below in order of decreasing precedence.
** abs not * / mod rem \ + sign\ - sign\ + & = /= < <= > >=
The lowest precedence is related to logic operators such as: and or nand nor xor . The logical operators operate on values of type bit or boolean , and also on one-dimensional arrays of these types. For array operands, the operation is applied between corresponding elements of each array providing an array of the same length as the result.

The relational operators =, /=, <, <=, > and >= must have the operands of the same type. They yield boolean results which are mainly used to build conditional statements:
 

if sum => 16 then ... -- boolean result
if sum <= 8 then ...
The equality operators = and /= can have operands of any type.

if "0000" /= 16 then ...

For composite types like arrays and records, two values are equal if all of their corresponding elements are equal. The sign operators + and - have their usual meaning on numeric operands. In principle, the arithmetical operators cannot be used with bit and bit_vector operands.
 

variable mot1, mot2: bit_vector(7 downto 0);
mot1 + mot2 -- not allowed on bit_vector
..
Note: In order to perform the arithmetical operations on bit vectors it is necessary to overload the arithmetical operators with the arithmetic functions defining the required algorithm. Such functions are currently provided within  numeric package.

The concatenation operator & forms new array with the contents of the right operand following the contents of the left operand. It can also concatenate a single new element to an array, or two individual elements to form an array.
 

type fiveb is array(0 to 4) of bit;
type tenb is array(0 to 9) of bit;
variable five_bits: fiveb;
variable ten_bits: tenb;
ten_bits := five_bits & "01011"

The multiplication and division operators (*,/) may be applied only to integer and floating point types.
 

q_val := 9/5; -- gives 1

The modulus mod and remainder rem operators only work on integer types. The absolute value abs operator works on any numeric type.
 

compteur := abs(-67) mod 7; gives 4


Variables and access type

VHDL provides the access types. Access types are used to declare values that access dynamically allocated variables. Dynamically allocated variables are referenced by an access value that acts like a pointer to the variable. The access types may be the scalar or composed objects. The keyword new is allocates the memory space for the indicated type. The keyword null indicates a non-allocated access (pointer).
 
  • scalar object (e.g. integer, enumerated type):
  • ..
    type ac_integer is access integer;
    type ac_enum is access etat;
    variable ac_var1,ac_var2: ac_integer;
    ...
    ac_var1 = new integer; -- initialized to integer'left
    ac_var1 = new integer'(3); -- initialized to 3
    ..
  • array objects; especially useful for array objects of undefined size. In case of access type the array can grow during the execution phase.
  • ..
    type ac_line is access string;
    variable ac_ligne1: ac_ligne;
    ...
    ac_ligne1 = new string'("bon jour VHDL");
    -- initialized to string "bon jour VHDL"
    ..
  • record objects; particularly useful for linked lists such as FIFO or LIFO
  • ..
    type record_type is ...;
    -- required for following access type declaration
    type ac_list is access record_type;
    type record_type is record
    node_var: integer;
    next_var: ac_list;
    prev_var: ac_list;
    end record;
    ...
    variable ac_head_var: ac_list;
    variable ac_tail_var: ac_list;
    variable ac_tmp_var: ac_list;
    ...
    ac_head_var = new record_type;
    ac_head_var.node_var := 40;
    ac_head_var.next_var := null;
    ac_head_var.prev_var := null;
    ...
    ac_tail_var := ac_head_var;
    ...
    ac_tmp_var:= ac_head_var;
    ...
    ac_head_var = new record_type;
    ac_tail_var.node_var := 20;
    ac_head_var.next_var := ac_tmp_var;
    ac_head_var.prev_var := null;
    ..

    Exercises