Déclarations

Où déclarer quoi ?

Certaines déclarations ne sont utilisables que dans un contexte donné. Le tableau ci-dessous indique où vous pouvez placer chaque déclaration.

Déclaration Architecture Processus Corps de sous-programme Paquetage Corps de paquetage
Signal Oui Non Non Non Non
Variable Non Oui Oui Non Non
Constante Oui Oui Oui Oui Oui
Type Oui Oui Oui Oui Oui
Sous-programme Oui Oui Oui Oui Oui
Corps de sous-programme Oui Oui Oui Non Oui
Composant Oui Non Non Oui Non

Déclaration de signaux

Dans une architecture, une déclaration de signal, ou de plusieurs signaux du même type, s’écrit comme ceci :

signal nom, ... nom : type;

Une déclaration de signal peut indiquer une valeur initiale en utilisant l’opérateur :=. La valeur initiale est définie par une expression dont la valeur doit être calculable à la construction. Autrement dit, cette expression ne peut contenir que des valeurs littérales et des constantes.

signal nom, ... nom : type := expression statique;

En simulation, la valeur initiale est affectée immédiatement à la date t=0t=0.

Un outil de synthèse pour FPGA utilisera cette indication pour imposer une valeur initiale aux bascules D du circuit.

Donner une valeur initiale à un signal combinatoire n’a pas de sens d’un point de vue matériel. Seuls les signaux à mémoire, c’est-à-dire ceux correspondant à des sorties de bascules D peuvent avoir une valeur initiale.

Les valeurs initiales des autres signaux sont ignorées par les outils de synthèse.

Déclaration de variables

Dans un processus, une déclaration de variable, ou de plusieurs variables du même type, s’écrit comme ceci :

variable nom, ... nom : type;

Contrairement aux signaux, qui transportent des données entre les instructions concurrentes d’une architecture, une variable est utilisée localement dans le processus ou le sous-programme où elle est déclarée.

Déclaration de constante

Dans une architecture, un processus ou un paquetage, une déclaration de constante s’écrit comme ceci :

constant nom : type := expression statique;

La valeur d’une constante est définie par une expression dont la valeur doit être calculable à la construction. Autrement dit, cette expression ne peut contenir que des valeurs littérales et des constantes.

Déclaration de type ou de sous-type

Type énuméré

Une type énuméré définit une liste de valeurs symboliques.

type nom du type is (nom, ... nom);

Lorsqu’un signal ou une variable est déclaré avec un type énuméré, sa valeur initiale correspond à la première valeur de la liste.

Dans l’exemple ci-dessous, on définit le type color_enum_t et deux constantes de ce type;

type color_enum_t is (BLACK, RED, GREEN, BLUE, YELLOW, MAGENTA, CYAN, WHITE);

constant TEXT_COLOR       : color_enum_t := NOIR;
constant BACKGROUND_COLOR : color_enum_t := CYAN;

Type tableau

Un type tableau permet de créer des collections ordonnées de valeurs du même type. VHDL autorise la définition de tableaux multidimensionnels.

type nom du type is array(intervalle, ... intervalle) of type;

Un aspect original des tableaux en VHDL est qu’ils n’imposent ni l’indice de départ, ni l’ordre des indices (voir également la section intervalles). Dans l’exemple ci-dessous, le type color_vec_t permet de créer des tableaux de trois éléments avec des indices décroissants commençant à 3 :

type color_vec_t is array(3 downto 1) of integer range 0 to 255;

signal c : color_vec_t;
...
-- Les trois écritures suivantes sont équivalentes :
c <= (100, 12, 54);

c <= (1 => 54, 2 => 12, 3 => 100);

c(1) <= 54;
c(2) <= 12;
c(3) <= 100;

Dans la déclaration d’un type tableau, on peut imposer les intervalles d’indices, ou les laisser libres. Dans le second cas, on parle de type tableau non contraint (unconstrained) et on indique seulement le type autorisé pour les indices de la manière suivante :

nom de type entier range <>

Par exemple, le type matrice d’entiers ci-dessous est utilisé pour définir deux signaux rect_mat et square_mat avec des tailles de tableaux différentes :

-- Type tableau d'entiers à deux dimensions, avec des indices positifs ou nuls.
type matrix_t is array(natural range <>, natural range <>) of integer;

-- Une matrice de taille 5x3
signal rect_mat   : matrix_t(0 to 4, 0 to 2);
-- Une matrice de taille 4x4
signal square_mat : matrix_t(0 to 3, 0 to 3);

Type structuré

Un type structuré permet de rassembler des valeurs de différents types dans une même structure de données.

type nom du type is record
   nom, ... nom : type;
   ...
   nom, ... nom : type;
end record;

Dans l’exemple ci-dessous, le type color_rec_t définit trois champs de type entier compris entre 0 et 255 :

type color_rec_t is record
    red, green, blue : integer range 0 to 255;
end record;

signal c : color_rec_t;
...
-- Les trois écritures suivantes sont équivalentes :
c <= (100, 12, 54);

c <= (red => 100, green => 12, blue => 54);

c.red   <= 100;
c.green <= 12;
c.blue  <= 54;

Déclaration de sous-type

Un sous-type définit un nouveau type dont les valeurs sont un sous-ensemble de celles d’un autre type.

subtype nom du sous-type is type;

On peut, par exemple, définir un type entier restreint à un intervalle :

subtype temperature_t is integer range -50 to 150;

On peut également spécialiser un type tableau non contraint :

type matrix_t is array(natural range<>, natural range<>) of integer;
subtype matrix3x3_t is matrix_t(0 to 2, 0 to 2);
...
signal m : matrix3x3_t;

Déclaration de composant

Une déclaration de composant décrit les entrées/sorties d’un composant à instancier dans une architecture. Elle permet, par exemple, de décrire les entrées/sorties d’une entité que vous voulez instancier, mais dont vous ne possédez pas le code source VHDL, ou pour lequel vous souhaitez conserver le choix entre différentes entités disponibles en bibliothèque.

Un composant sans paramètre générique s’écrit de la manière suivante :

component nom du composant
   port(
      nom, ... nom : mode type;
      ...
      nom, ... nom : mode type
   );
end component;

Les paramètres génériques sont déclarés dans une clause generic map() :

component nom du composant
   generic(
      nom, ... nom : type;
      ...
      nom, ... nom : type
   );
   port(
      nom, ... nom : mode type;
      ...
      nom, ... nom : mode type
   );
end component;

Les déclarations de ports et de paramètres respectent la même syntaxe que dans une entité.

La notion de composant offre une grande flexibilité. En séparant la déclaration des entités et la déclaration des composants, elle permet de différer le moment du choix de l’entité à utiliser pour chaque instance. Ce choix est précisé séparément dans une déclaration de configuration, qui associe les composants, ou des instances précises, avec les entités disponibles en bibliothèque.

La notion de configuration ne sera pas détaillée dans ce document.

En pratique, il est fréquent que la déclaration d’un composant soit un simple copier-coller de la déclaration d’une entité existante. Dans ce cas, il est plus simple de ne pas déclarer de composant et d’utiliser une instruction d’instanciation d’entité.

Sous-programmes

Comme les langages de programmation, VHDL possède une notion de sous-programme. Un sous-programme possède un nom, une liste de paramètres, des déclarations locales et un bloc d’instructions séquentielles. On distingue deux sortes de sous-programmes :

VHDL distingue la déclaration d’un sous-programme, dans lequel on ne décrit que son interface, et le corps de ce sous-programme, dans lequel on ajoute les déclarations locales et les instructions.

On déclare des sous-programmes en utilisant la syntaxe suivante :

procedure nom de la procédure(paramètres;... paramètres);
function nom de la fonction(paramètres;... paramètres) return type du résultat;

Si le nom d’une fonction est écrit entre guillemets, la fonction définit alors un opérateur. Par exemple, les opérations d’addition et de multiplication sur les matrices pourraient être déclarées de cette manière :

function "+"(a, b : matrix_t) return matrix_t;
function "*"(a, b : matrix_t) return matrix_t;
function "*"(a : matrix_t; b : integer) return matrix_t;
function "*"(a : integer; b : matrix_t) return matrix_t;

Le corps d’un sous-programme reprend la déclaration, et lui ajoute les déclarations locales et les instructions :

procedure nom de la procédure(paramètres;... paramètres) is
    declaration
    ...
    declaration
begin
    instruction séquentielle
    ...
    instruction séquentielle
end nom de la procédure;

function nom de la fonction(paramètres;... paramètres) return type du résultat is
    declaration
    ...
    declaration
begin
    instruction séquentielle
    ...
    instruction séquentielle
end nom de la fonction;

Dans la liste des paramètres, on sépare par des point-virgules des groupes de paramètres de même mode et de même type. Une déclaration de paramètres peut prendre différentes formes résumées par la syntaxe suivante :

nature nom, ... nom : mode type

En pratique, on peut omettre certains de ces mots-clés et on utilisera les formes suivantes :

nom, ... nom : type
nom, ... nom : out type
nom, ... nom : inout type
signal nom, ... nom : out type
signal nom, ... nom : inout type