Il réalise la conversion d'un type vers un autre type.
Exemple :
On veut utliser un type de la taille d'un octet (0 à 255). Pour cela on va déclarer ce nouveau type octet
type octet is range 0 to 255;
signal cpt : integer;
signal valeur : octet;
Le passage de l'entier à l'octet peut utiliser la transtypage :
octet <= octet(cpt);
Le sous-type permet de déclarer un type qui est inclus dans un autre type, qui le rend compatible avec le type dans lequel il est inclus.
Exemple avec l'octet de 0 à 255
subtype octet is integer range 0 to 255;
signal cpt : octet;
Ces fonctions sont groupées dans le paquetage ieee.numeric_std
Les fonctions
Les transtypages
Elles permettent de stocker des résultats intermédiaires ou encore être utilisées dans des boucles qui permettent de construire un circuit en répétant une structure élémentaire de ce circuit.
Les variables n'ont pas de réprésentation physique et ont un comportement uniquement séquentiel et non concurrent.
Elle sont déclarées avec le mot clé variable
C'est un sous-programme qui ne possède pas de valeur de retour. Une procédure est déclarée dans une architecture ou une autre procédure ou une autre fonction. Les procédures n'ont pas de réprésentations physiques.
Syntaxe :
procedure (liste des parametres) is
-- déclaration des variables locales
begin
-- traitement
end procedure;
La liste des paramètres contient les paramètres séparés par un point virgule avec la syntaxe :
categorie nom : sens type := valeur par défaut
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
-- contient les fonctions de conversion de type
use IEEE.NUMERIC_STD.ALL;
entity compteur is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
en : in STD_LOGIC;
tc : out STD_LOGIC;
Q : out STD_LOGIC_VECTOR(3 downto 0));
end compteur;
architecture arch_compteur of compteur is
-- sous type quartet sur 4 bits
subtype quartet is natural range 0 to 15;
-- procédure qui ajoute 1 au signal de type quartet
procedure incrementer(signal c : inout quartet) is
begin
c <= c + 1;
end procedure;
-- signal qui utilise le sous-type défini
signal cpt : quartet;
constant Nmax : natural := 9 ;
begin
-- conversion du quartet en vecteur
Q <= std_logic_vector(to_unsigned(cpt,4));
tc <= '1' when cpt = Nmax else '0';
comptage: process(clk,reset)
begin
if reset='0' then
cpt <= 0; -- cpt dérive d'entier
elsif rising_edge(clk) and en ='1' then
if cpt < Nmax then
incrementer(cpt);
else
cpt <= 0; -- cpt dérive d'entier
end if;
end if;
end process comptage;
end arch_compteur;
natural est un entier positif
La procédure ajoute 1 au paramètre qui est un paramètre utilisé en entrée et sortie, ce qui fait que la modification réalisée dans la procédure sera transmise au processus.
cpt n'est plus un vecteur mais un entier, ceci explique l'utilisation de la valeur 0 au lieu du vecteur de 0.
La sortie Q est un vecteur, il faut convertir l'entier cpt en vecteur. Il n'existe pas de fonction de conversion directe, il faut donc utiliser deux conversions, la premier vers un non signé et la deuxième vers un vecteur.
C'est un sous-programme qui possède une valeur de retour. Une fonction est déclarée dans une architecture ou une autre procédure ou une autre fonction. Les fonctions n'ont pas de réprésentations physiques.
Syntaxe :
function (liste des parametres) return type is
-- déclaration des variables locales
-- et de la valeur renvoyée par return
begin
-- traitement
return retour;
end function;
La liste des paramètres contient les paramètres séparés par un point virgule avec la syntaxe :
categorie nom : sens type := valeur par défaut
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
-- contient les fonctions de conversion de type
use IEEE.NUMERIC_STD.ALL;
entity compteur is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
en : in STD_LOGIC;
tc : out STD_LOGIC;
Q : out STD_LOGIC_VECTOR(3 downto 0));
end compteur;
architecture arch_compteur of compteur is
-- sous type quartet sur 4 bits
subtype quartet is natural range 0 to 15;
-- procédure qui ajoute 1 au signal de type quartet
function incrementer(signal c : in quartet) return quartet is
variable valeur : quartet;
begin
valeur := c + 1;
return valeur;
end function;
-- signal qui utilise le sous-type défini
signal cpt : quartet;
constant Nmax : natural := 9 ;
begin
-- conversion du quartet en vecteur
Q <= std_logic_vector(to_unsigned(cpt,4));
tc <= '1' when cpt = Nmax else '0';
comptage: process(clk,reset)
begin
if reset='0' then
cpt <= 0; -- cpt dérive d'entier
elsif rising_edge(clk) and en ='1' then
if cpt < Nmax then
cpt <= incrementer(cpt);
else
cpt <= 0; -- cpt dérive d'entier
end if;
end if;
end process comptage;
end arch_compteur;
Un paquetage permet de déclarer un ou plusieurs type de données avec les procédures et fonctions de traitement de ces nouveaux types ainsi que les fonctions de conversions
Un paquetage est composé de deux blocs qui sont l'entête qui contient les déclarations et le corps qui contient les codes des procedures et fonctions
Entête
package nom is
−− declarations
end nom;
Corps
package body nom is
−− codage des procédures et fonctions
end nom;
On propose d'écrire un paquetage qui contient la définition du type quartet ainsi que la procédure d'incrémentation et la fonction de conversion d'un quartet en vecteur de bits.
On ajoutera à ce paquetage la surcharge de l'opérateur + pour le quartet. La surcharge de l'opérateur + est simplement une fonction de nom "+" qu'il faut déclarer dans l'entête du paquetage et définir dans le corps du paquetage.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
package quartet_pkg is
-- définition du type quartet
type quartet is range 0 to 15;
-- définition du type vecteur de 4 bits
subtype stdvec4 is std_logic_vector(3 downto 0);
-- procédure qui incrémente le quartet
procedure incrementer(signal c : inout quartet);
-- surcharge de l'opérateur + pour l'octet
function "+"(a,b:quartet) return quartet;
-- fonction de conversion d'un quartet en vecteur de bits
function octet_to_stdlogicvector(signal c : in quartet) return stdvec4;
end quartet_pkg;
-- incrémenter un quartet
-- paramètre en entrée et en sortie : c de type quartet
package body quartet_pkg is
procedure incrementer(signal c : inout quartet) is
begin
c <= c + 1;
end procedure;
-- surcharge de l'opérateur + pour le quartet
function "+"(a,b:quartet) return quartet is
variable c: quartet;
begin
c := quartet(integer(a) + integer(b));
return c;
end function;
-- conversion d'un quartet en vecteur de bits
-- paramètre en entrée : c de type quartet
-- valeir de retour : vecteur de 4 bits
function octet_to_stdlogicvector(signal c : in quartet) return stdvec4 is
variable s : std_logic_vector(3 downto 0);
variable ci : natural;
begin
-- conversion du quartet en entier positif
ci := natural(c);
-- conversion d'un entier positif en vecteur de 4 bits
s := std_logic_vector(to_unsigned(ci,4));
-- valeur de retour
return s;
end function;
end quartet_pkg;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
-- contient les fonctions de conversion de type
use IEEE.NUMERIC_STD.ALL;
-- inclusion du paquetage situé dans le même répertoire
use WORK.quartet_pkg.all;
entity compteur is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
en : in STD_LOGIC;
tc : out STD_LOGIC;
Q : out STD_LOGIC_VECTOR(3 downto 0));
end compteur;
architecture arch_compteur of compteur is
-- signal qui utilise le sous-type défini
signal cpt : quartet;
constant Nmax : quartet := 9 ;
begin
-- conversion du quartet en vecteur
Q <= octet_to_stdlogicvector(cpt);
tc <= '1' when cpt = Nmax else '0';
comptage: process(clk,reset)
begin
if reset='0' then
cpt <= 0; -- cpt dérive d'entier
elsif rising_edge(clk) and en ='1' then
if cpt < Nmax then
cpt <= cpt + 1 ; -- utilisation + pour quartet
else
cpt <= 0; -- cpt dérive d'entier
end if;
end if;
end process comptage;
end arch_compteur;
Cette structure de contrôle permet de créer un circuit en répétant une structure. Elle s'applique en dehors du processus.
etiquette: | for variable in liste generate |
begin | |
-- traitement | |
end generate etiquette; |
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
-- package qui permet d'écrire e = i
use IEEE.STD_LOGIC_UNSIGNED.ALL;
-- decodeur 3 vers 8
entity decodeur is
port ( e : in STD_LOGIC_VECTOR(2 downto 0);
S : out STD_LOGIC_VECTOR(0 to 7));
end decodeur;
architecture architecture_decodeur of decodeur is
begin
-- i varie entre 0 et 7
circuit: for i in 0 to 7 generate
begin
-- le bit i de la sortie est affecté à 1
-- si l'entrée e est égal à la valeur i
S(i) <= '1' when e = i else '0' ;
end generate;
end architecture_decodeur;
Celles-ci s'appliquent à l'intérieur des processus.
etiquette: | for variable in liste loop |
begin | |
-- traitement | |
end loop etiquette; |
etiquette: | loop |
begin | |
-- traitement | |
end loop etiquette; |
etiquette: | while expression booléenne VRAIE loop |
begin | |
-- traitement | |
end loop etiquette; |
Les structures de contrôle dans les boucles
Un composant générique est un composant qui est paramétrable, comme par exemple un décodeur de n vers 2n, ou encore un diviseur de fréquence par N
L'entité comprend, en plus, une instruction generic, la syntaxe de l'entité d'un composant générique est :
entity nom is
generic (liste des parametres)
map (liste des signaux)
end nom;
La liste des paramètres, séparés par une virgule, respecte la syntaxe :
nom : type := valeur par défaut;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
-- package qui permet d'écrire e = i
use IEEE.STD_LOGIC_UNSIGNED.ALL;
-- decodeur 3 vers 8
entity decodeur is
generic ( n : integer := 3);
port ( e : in STD_LOGIC_VECTOR(n-1 downto 0);
S : out STD_LOGIC_VECTOR(0 to 2**n-1));
end decodeur;
architecture architecture_decodeur of decodeur is
begin
-- i varie entre 0 et 2^n-1
circuit: for i in s'range generate
begin
-- le bit i de la sortie est affecté à 1
-- si l'entrée e est égal à la valeur i
S(i) <= '1' when e = i else '0' ;
end generate;
end architecture_decodeur;
Les valeurs obtenues en simulation confirment que le système fonctionne toujours.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_unsigned.all;
-- fonctions mathématiques
use ieee.math_real.all;
-- diviseur par clkdiv
-- frequence tc = frequence clk / clkdiv
entity diviseur is
Generic(clkdiv : integer := 2);
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
tc : out STD_LOGIC);
end diviseur;
architecture architecture_diviseur of diviseur is
-- calcul du nombre de bits en fonction de la valeur maximale
constant Nbits : natural := integer(ceil(log2(real(clkdiv+1))));
signal cptdiv : std_logic_vector(Nbits downto 0);
begin
-- processus de compptage de 0 à clkdiv-1
-- pou diviser par clkdiv
comptage: process(clk)
begin
if reset = '0' then
cptdiv <= (others => '0');
elsif rising_edge(clk) then
if cptdiv < clkdiv-1 then
cptdiv <= cptdiv + 1;
else
cptdiv <= (others => '0');
end if;
end if;
end process comptage;
retenue: process(cptdiv)
begin
if cptdiv=clkdiv-1 then
tc <= '1';
else
tc <= '0';
end if;
end process retenue;
end architecture_diviseur;
On doit réaliser un diviseur de fréquence par clkdiv, cette valeur doit être représentée sur n bits avec la relation :
clkdiv ≤ 2n-1. Mathématiquement, il s'agit de prendre le n ≥ log2(clkdiv+1).
Cela se calcule en utilisant les fonctions mathématiques su paquetage math_real avec la relation :
Le reste du code est un compteur avec sortie de retenue sans sortie de comptage.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
-- compteur synchrone décimal
-- reset asynchrone
-- validation synchrone
-- sortie de retenue
entity compteur is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
en : in STD_LOGIC;
tc : out STD_LOGIC;
Q : out STD_LOGIC_VECTOR(3 downto 0));
end compteur;
architecture arch_compteur of compteur is
-- declaration du composant diviseur
component diviseur is
Generic(clkdiv : integer := 2);
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
tc : out STD_LOGIC);
end component;
signal endiv : STD_LOGIC ;
signal cpt : STD_LOGIC_VECTOR (3 downto 0);
constant Nmax : integer := 9 ;
begin
-- instanciation physique du composant diviseur
chipdiviseur : diviseur generic map (clkdiv => 4)
port map( clk => clk, reset => reset,
tc => endiv );
Q <= cpt;
tc <= '1' when cpt = Nmax else '0';
comptage: process(clk,reset)
begin
if reset='0' then
cpt <= (others => '0');
-- compteur synchronisé sur le diviseur avec endiv
elsif rising_edge(clk) and en ='1'and endiv = '1' then
if cpt < Nmax then
cpt <= cpt + 1;
else
cpt <= (others => '0');
end if;
end if;
end process comptage;
end arch_compteur;
La division de fréquence est précisée au moment de l'instanciation du composant et non dans la déclaration.
La période du compteur en sortie est bien 4 fois la période de l'horloge clk. On a bien un compteur avec division de fréquence par 4.
Les structure assert et report permettent d'afficher des commentaires pendant la simulation
report seul affiche un message pendant la simulation
assert expression booléenne report message severity niveau
Utiliser des diagrammes des temps pour afficher les résultats de la simulation est bien, mais quelques fois, comme en combinatoire, une table de vérité est suffisante. Pour cela, on va maintenant étudier, l'écriture des résultats dans des fichiers textes.
Il faut inclure les paquetages std.textio et ieee.std_logic_textio
Cet affichage de fait en deux étapes :
Note : tant que writeline n'est pas appelé, les fonctions write concatènent les messages dans le buffer
Le programme ci-contre calcule le nombre de bits nécessaire pour stocker une valeur. La fonction mathématique a déjà été décrite dans l'exemple avec le diviseur.
Le processus se termine toujours par wait pour stoper le programme, car il ne faut oublier que process est une boucle temporelle infinie.
library ieee;
use ieee.std_logic_1164.all;
-- fonctions mathématiques
use ieee.math_real.all;
-- sortie texte
use std.textio.all;
use ieee.std_logic_textio.all;
entity calcul is
end calcul;
architecture arch_calcul of calcul is
begin
calculproc: process
variable ligne : line;
-- valeur à utiliser
variable valeur : natural := 50000000;
variable nbits : natural ;
variable Nmax : natural ;
variable imax : natural ;
begin
write(ligne, string'("Calcul du nombre de bits"));
writeline(output,ligne);
write(ligne,string'("valeur = "));
write(ligne,natural'image(valeur));
writeline(output,ligne);
nbits := natural(ceil(log2(real(valeur+1))));
Nmax := 2**nbits - 1;
write(ligne,string'("nbits = "));
write(ligne,natural'image(nbits));
writeline(output,ligne);
write(ligne,string'("Valeur maximale = "));
write(ligne,natural'image(Nmax));
writeline(output,ligne);
if Nmax >= valeur then
write(ligne,string'("OK"));
else
write(ligne,string'("insuffisant"));
end if;
writeline(output,ligne);
wait; -- fin de processus
end process calculproc;
end arch_calcul;
Dans le programme présédent, il faut compiler pour chaque nouvelle valeur, on propose, ici, de demander à l'utilisateur de saisir la valeur. On va donc utiliser les fonctions de lecture de données à partir de l'entrée standard qui est le clavier qui se fait également en deux étapes
Le code ci-contre montre la partie du code précédent qui a été modifié pour permettre la saisie de la valeur au clavier
Si la valeur saisie n'est pas un entier, alors on arrête la simulation en affichant un message d'erreur.
calculproc: process
variable ligne, clavier : line;
-- valeur à utiliser
variable valeur : natural := 10000 ;
variable nbits : natural ;
variable Nmax : natural ;
variable imax : natural ;
variable conforme : boolean ;
begin
write(ligne, string'("Entrer la valeur"));
writeline(output,ligne);
readline(input,clavier);
read(clavier,valeur,conforme);
assert conforme report "ERREUR de saisie" severity failure;
write(ligne,string'("valeur = "));
write(ligne,natural'image(valeur));
writeline(output,ligne);
nbits := natural(ceil(log2(real(valeur+1))));
Nmax := 2**nbits - 1;
write(ligne,string'("nbits = "));
write(ligne,natural'image(nbits));
writeline(output,ligne);
write(ligne,string'("Valeur maximale = "));
write(ligne,natural'image(Nmax));
writeline(output,ligne);
if Nmax >= valeur then
write(ligne,string'("OK"));
else
write(ligne,string'("insuffisant"));
end if;
writeline(output,ligne);
wait; -- fin de processus
end process calculproc;
L'accès au fichier utilise un identificateur de fichier (handle) qui est de type file.
Un type fichier est défini avec la syntaxe :
type nom du type is file of type de l'élément
le type de l'élément est un type de donnée connu ou bien défini avec la syntaxe type
Il existe un type de fichier prédéfini de type fichier texte : text
Ensuite on déclare une variable avec de type, le nom de la variable est l'indentificateur de fichier qui sera utilisé à la place de output dans writeline et à la place de input dans readline.
La fonction d'ouverture, open, assigne cet identificateur au fichier représenté par son nom. La fonction de fermeture, close, annule cette assignation en vidant les buffers d'échange de données.
L'accès au contenu d'un fichier commence obligatoirement par l'ouverture, effectue l'échange des données, et se termine obligatoirement par la fermeture.
Elle est réalisé avec la fonction :
file_open(statut,idfichier,nom du fichier,mode accès) avec
Elle se fait avec la fonction file_close(idfichier)
Elle se fait avec la déclaration des variables avec la syntaxe
file idfichier : nom du type open mode d'accès is nom du fichier;
Elle se fait automatiquement en fin de processus
On utilise les fichiers pour enregister les résultats de simulation sous la forme d'une table de vérité dans un fichier texte. On applique ce principe au décodeur.
Test bench
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.all;
use STD.TEXTIO.all;
use IEEE.STD_LOGIC_TEXTIO.all;
entity codeur_tb is
end codeur_tb;
architecture tb_arch of codeur_tb is
procedure entetetobuffer(variable ligne : inout line;variable v : in unsigned;variable c : in string) is
begin
for i in v'range loop
write(ligne,HT); -- tabulation
write(ligne,string'(c));
write(ligne,integer'image(i));
end loop;
end procedure;
procedure Lignetobuffer(variable ligne : inout line;variable v : in unsigned) is
begin
for i in v'range loop
write(ligne,HT); -- tabulation
write(ligne,std_logic'(v(i)));
end loop;
end procedure;
-- declaration composant a simuler : codeur
component codeur
port ( e : in STD_LOGIC_VECTOR(0 to 7);
S: out STD_LOGIC_VECTOR(2 downto 0));
end component;
-- signaux relies au composant codeur
signal tbe : STD_LOGIC_VECTOR(0 to 7); -- entree 8 bits
signal tbS : STD_LOGIC_VECTOR(2 downto 0); -- sortie 3 bits
begin
-- instanciation physique du composant codeur
code83: codeur port map(e => tbe, S => tbS);
-- process de simulation avec boucle et variable
simulation: process
variable entree : unsigned(0 to 7) := b"1000_0000";
variable sortie : unsigned(2 downto 0);
file ftable : text open WRITE_MODE is "codeur.txt" ;
variable ligne : line;
variable variableentree : string(1 to 1) := "e" ;
variable variablesortie : string(1 to 1) := "S" ;
begin
sortie := unsigned(tbS);
entetetobuffer(ligne,entree,variableentree);
write(ligne,HT);
write(ligne,string'("|"));
entetetobuffer(ligne,sortie,variablesortie);
writeline(ftable,ligne);
boucle : while entree /= 0 loop
tbe <= std_logic_vector(entree);
wait for 50 ns;
Lignetobuffer(ligne,entree);
write(ligne,HT);
write(ligne,string'("|"));
sortie := unsigned(tbS);
Lignetobuffer(ligne,sortie);
writeline(ftable,ligne);
wait for 50 ns;
entree := entree srl 1;
end loop boucle;
wait; -- fin du process de simulation
end process simulation;
end;
Table de vérité des résultats, contenu de "codeur.txt"
e0 e1 e2 e3 e4 e5 e6 e7 | S2 S1 S0
1 0 0 0 0 0 0 0 | 0 0 0
0 1 0 0 0 0 0 0 | 0 0 1
0 0 1 0 0 0 0 0 | 0 1 0
0 0 0 1 0 0 0 0 | 0 1 1
0 0 0 0 1 0 0 0 | 1 0 0
0 0 0 0 0 1 0 0 | 1 0 1
0 0 0 0 0 0 1 0 | 1 1 0
0 0 0 0 0 0 0 1 | 1 1 1
Par rapport au testbench classique on utilise un processus et une boucle qui génère les différentes valeurs de l'entrée. Afin de générer les différentes valeurs nécessaires à la vérification du fonctionnement, on utilise un vecteur que l'on décale à droite avec une variable, ce qui fait que dans ce cas on peut utliser l'opérateir srl. On arrête lorsque toutes les entrées valent 0.
Afin qu'il n'y aie pas de décalage temporel à cause de l'utilisation de signaux et variables, on ajoute un délai entre chaque changment et entre changement et écriture dans le fichier.
On a ajouté une ouverture de fichier de type texte implicite en écriture et création, le fichier est détruit à chaque exécution de la simulation.
Pour écrire les vecteurs d'entrée et de sortie bit par bit, on utilise une procédure qui permet d'afficher chaque bit du vecteur séparé par une tabulation.
On utilise également une procédure pour afficher les variables d'entrée et de sortie en entête de fichier. Pour cela on se sert des indices des vecteurs d'entrée et de sortie.
Si on veut encore plus de codes, comme des codes de mémoire, processeur, interface, ... , on peut consulter le site dédié opencores.
Le code VHDL qui suit est celui d'un générateur de valeurs pseudo-aléatoires en utilisant le corps de Galois comme cela a déjà été présenté dans le chapitre logique séquentielle.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity galois is
Generic ( N : integer := 4;
polynome : integer := 2#1001#;
vinit : integer := 2#0000#);
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
en : in STD_LOGIC;
y : out STD_LOGIC_VECTOR(0 to N-1));
end galois;
architecture arch_galois of galois is
signal qreg : STD_LOGIC_VECTOR (0 to N-1);
signal h, yk : STD_LOGIC_VECTOR(0 to N-1);
signal x : STD_LOGIC;
begin
h <= conv_std_logic_vector(polynome,N);
registre: process(clk,reset)
begin
if reset = '0' then
qreg <= conv_std_logic_vector(vinit,N);
elsif rising_edge(clk) and en = '1' then
qreg <= x & qreg(0 to N-2);
else
qreg <= qreg;
end if;
end process registre;
yk(N-1) <= qreg(N-1) and h(N-1);
multi: for i in N-2 downto 0 generate
yk(i) <= yk(i+1) xor (qreg(i) and h(i));
end generate;
x <= yk(0);
y <= qreg;
end arch_galois;