Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/TP 4
L'objectif de ce troisième TP est enfin de lire les scan-codes d'un clavier. Nous allons construire petit à petit un ensemble de composants capable de cet exploit en partant du travail déjà réalisé dans le TP précédent. Nos techniques de conceptions seront identiques, c'est-à-dire facile à comprendre mais pas très professionnelles. Pour rendre ce travail un peu plus conformes aux règles de construction des horloges, nous avons ajouté en fin de ce chapitre une section spécifique (Mise en conformité des horloges). Cette section aura toute son importance pour des élèves ingénieurs mais pourra être laissée de côté par les étudiants L1 et L2.
Introduction
[modifier | modifier le wikicode]On a retenu du TP précédent que l'appui sur une touche était compliqué car on recevait plus de fronts d'horloge (qui étaient comptés dans le TP précédent en question ) que prévu. On en attendait 11 et finalement on en recevait 33.
Techniquement cela peut s'expliquer par le fonctionnement du clavier. Lorsqu'on appui sur une touche, on finit par la relâcher et c’est ce relâchement qui envoie lui aussi des codes. On résume ci-dessous le fonctionnement des claviers à travers l'histoire (tiré de Wikipédia anglais) adaptée au clavier AZERTY :
Touche | set 1 (IBM PC XT) | set 2 (IBM PC AT) | set 3 (IBM 3270 PC) | |||
---|---|---|---|---|---|---|
Appui | relâchement | Appui | relâchement | Appui | relâchement | |
Q (lettre normale) | 1E |
9E |
1C |
F0 1C |
1C |
F0 1C
|
Retour Chariot / Entrée (clavier principal) | 1C |
9C |
5A |
F0 5A |
5A |
F0 5A
|
Retour Chariot / Entrée (clavier numérique) | E0 1C |
E0 9C |
E0 5A |
E0 F0 5A |
79 |
F0 79
|
Touche Windows gauche | E0 5B |
E0 DB |
E0 1F |
E0 F0 1F |
8B |
F0 8B
|
Touche Windows droite | E0 5C |
E0 DC |
E0 27 |
E0 F0 27 |
8C |
F0 8C
|
On s'intéressera par la suite au clavier "set2 (BM PC AT)" du tableau ci-dessus. |
Un article en anglais détaille les scancodes du clavier QWERTY. Notez que les scancodes codent la position des touches et par conséquent passer du clavier QWERTY au clavier AZERTY ne présente aucune difficulté.
Lecture partielle d'un clavier PS/2
[modifier | modifier le wikicode]L'objectif de ce TP est de réaliser la lecture des codes (scancodes) d'un clavier sur un port PS/2. À la différence du TP précédent, on doit retrouver maintenant le scancode de la touche appuyée et non plus le nombre de fronts d'horloge que cet appui a généré.
Nous allons compléter le TP précédent, c'est-à-dire utiliser l'horloge échantillonnée (notée "Sortie" dans le schéma ci-dessous) comme horloge du compteur et du registre à décalage. Cette façon de faire n’est pas bonne en tous points, mais elle est simple et conduit à un séquenceur à trois états (schématisé par un GRAFCET dans la figure ci-dessous).
On parle de lecture partielle car notre montage final sera incapable :
- de savoir si ce qu’il affiche est un scancode d'appui ou de relâchement
- de faire la différence entre une touche normale et une touche étendue (qui envoie 0xE0 en plus)
Travail à réaliser et sa correction
[modifier | modifier le wikicode]Comme dans les travaux précédents le travail à réaliser est donné sous le forme d'un schéma.
Schéma à réaliser
[modifier | modifier le wikicode]On donne le schéma global de ce que vous allez réaliser ci-dessous. Comme d'habitude le travail à réaliser est présenté dans un rectangle bleu clair. Ce rectangle permet de déterminer en un seul coup d'œil les entrées et les sorties. Le rectangle gris représente quant à lui le travail déjà réalisé dans le TP1.
Par rapport au TP précédent on garde le compteur sur 8 bits mais il est actif sur front descendant maintenant, et toujours commandé par « sortie » qui commande aussi un registre à décalage de 11 bits. Après un transfert correct on trouvera dans ce registre les 8 bits de données, le bit de start, le bit de stop et le bit de parité. Seuls les 8 bits intéressants (Q1 ... Q8) seront transférés dans le registre d'affichage. On a ajouté d’autre part un circuit combinatoire détectant l'état cmpt=11. Sa sortie est utilisée par un séquenceur décrit par un grafcet. Son objectif est qu'une fois les 11 fronts d'horloge détectés, le transfert est lancé pour les 8 bits intéressants dans le registre d'affichage, puis il remet le compteur à 0 afin d'attendre le début d'un autre transfert.
La transformation d'un GRAFCET en programme VHDL est examinée dans un autre projet qu’il vous est fortement conseillé de parcourir.
Il est clair que la synthèse demandée est complexe. Comme toute synthèse complexe elle peut être décomposée en deux parties distinctes (voir ici pour des compléments) :
- un séquenceur
- une partie opérative ou chemin de données
On remarquera que puisque ces deux parties utilisent une horloge commune, le séquenceur fonctionne sur front montant tandis que la partie opérative fonctionne sur front descendant.
Aucun test de parité n'est réalisé dans cette version. Les évolutions possibles sont donc ce test de parité et un séquencement un peu plus proche de l'état du bus.
Quelques scancodes :
- scancode hexadécimal pour clavier AZERTY
Touche | Scancode | Touche | Scancode | Touche | Scancode |
b | 0x32 | d | 0x23 | f | 0x2B |
g | 0x34 | h | 0x33 | j | 0x3B |
q | 0x1C | s | 0x1B | t | 0x2C |
Éléments de VHDL donnés pour commencer votre travail
[modifier | modifier le wikicode]L'entité globale est :
entity tp3 is
port(
PS2_Clk_M16, PS2_Data_M15,Clk_T9, reset_L14 : in std_logic;
sorties_aff : out std_logic_vector(6 downto 0);
aff : out std_logic_vector(3 downto 0));
end tp3;
L'ensemble des composants à réaliser est maintenant :
component Counter8 -- compteur {{Unité|8|bits}}
port(horloge,reset : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component ShiftReg -- registre a decalage {{Unité|8|bits}}
port(clk,entree : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component ShiftReg11 -- registre a decalage {{Unité|11|bits}} mais seuls {{Unité|8|bits}} sont sortis !
port(clk,entree : in std_logic;
q8 : out std_logic_vector(7 downto 0));
end component;
component comb -- filtrage des aleats
port(
entrees : in std_logic_vector(7 downto 0);
eQ : in std_logic;
F : out std_logic);
end component;
component dflipflop --bascule D
port(
d,clk : in std_logic;
q : out std_logic);
end component;
component tp1 -- realise en TP 1
port (
clk : in std_logic;
entrees : in std_logic_vector(7 downto 0);
s7segs : out std_logic_vector(6 downto 0);
aff : out std_logic_vector(3 downto 0));
end component;
component seq -- séquenceur ({{Abréviation|GRAFCET|graphe fonctionnel de commande par étapes et transitions}})
port(
clk, cmpt_is_11,init : in std_logic;
reset,transfert : out std_logic
);
end component;
component compare -- comparateur
port (
entrees : in std_logic_vector(7 downto 0);
s_cmpt_is_11 : out std_logic
);
end component;
On utilisera complètement le travail fait en TP 1 et partiellement celui de TP 2.
Questions
[modifier | modifier le wikicode]Relever certains scancodes en plus de ceux fournis en exemple pour les tests, et en particulier celui des flèches directionnelles.
Les flèches directionnelles (pas celles du clavier numérique) ont comme scancode :
- gauche : Ox6B
- droite : 0x74
- haut : 0x75
- bas : 0x72
Elles envoie toutes le code 0xE0 avant le scancode. On donne sans plus de commentaires la solution du TP 3 ici :
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity tp3 is
port(
PS2_Clk_M16, PS2_Data_M15,Clk_T9, reset_L14 : in std_logic;
sorties_aff : out std_logic_vector(6 downto 0);
aff : out std_logic_vector(3 downto 0));
end tp3;
architecture atp3 of tp3 is
component Counter8 -- {{Unité|8|bits}}
port(horloge,reset : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component ShiftReg -- {{Unité|8|bits}}
port(clk,entree : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component ShiftReg11 -- {{Unité|11|bits}}
port(clk,entree : in std_logic;
q8 : out std_logic_vector(7 downto 0));
end component;
component comb -- elimination des aléats
port(
entrees : in std_logic_vector(7 downto 0);
entree : in std_logic;
F : out std_logic);
end component;
component dflipflop -- bascule D
port(
d,clk : in std_logic;
q : out std_logic);
end component;
component tp1 -- voir le TP1
port (
clk : in std_logic;
entrees : in std_logic_vector(7 downto 0);
s7segs : out std_logic_vector(6 downto 0);
aff : out std_logic_vector(3 downto 0));
end component;
component seq
port(
clk, cmpt_is_11,init : in std_logic;
reset,transfert : out std_logic
);
end component;
component compare
port (
entrees : in std_logic_vector(7 downto 0);
s_cmpt_is_11 : out std_logic
);
end component;
signal sig_q8 : std_logic_vector(7 downto 0);
signal sig_q,sig_d,sig_reset,sig_transfert,sig_cmpt_is_11 : std_logic;
signal sig_cmpt8 : std_logic_vector(7 downto 0);
signal bus_8 : std_logic_vector(7 downto 0);
signal reset_L14_OR_sig_reset : std_logic;
begin
reset_L14_OR_sig_reset <=reset_L14 OR sig_reset;
i1:ShiftReg port map(clk=>Clk_T9,entree=>PS2_Clk_M16,q=>sig_q8);
i2:comb port map(entrees=>sig_q8,entree=>sig_q,F=>sig_d);
i3:dflipflop port map(d=>sig_d,q=>sig_q,clk=>Clk_T9);
i4:Counter8 port map(horloge=>sig_q ,reset=>reset_L14_OR_sig_reset,q=>sig_cmpt8);
i5:tp1 port map(clk=>Clk_T9,entrees=>bus_8,s7segs=>sorties_aff,
aff=>aff);
i6:Shiftreg11 port map(clk=>sig_q,entree=>PS2_Data_M15,q8=>bus_8);
i7:seq port map(clk=>Clk_T9,init=>reset_L14,reset=>sig_reset,transfert=>sig_transfert,
cmpt_is_11 => sig_cmpt_is_11);
i8:compare port map(entrees => sig_cmpt8,s_cmpt_is_11=>sig_cmpt_is_11);
end atp3;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity Counter8 is
port(horloge,reset : in std_logic;
q : out std_logic_vector(7 downto 0));
end Counter8;
architecture aCounter8 of Counter8 is
signal qs : std_logic_vector(7 downto 0);
begin
process(horloge,reset) begin
if reset='1' then
qs<="00000000";
else
if(horloge'event and horloge='0') then
qs <= qs + 1;
end if;
end if;
end process;
q <= qs;
end aCounter8;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity ShiftReg is
port(clk,entree : in std_logic;
q : out std_logic_vector(7 downto 0));
end ShiftReg;
architecture aShiftReg of ShiftReg is
signal dataq : std_logic_vector(7 downto 0);
begin
process(clk) begin
if clk'event and clk='0' then
dataq <= entree& dataq(7 downto 1);
end if;
end process;
q<=dataq;
end aShiftReg;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity ShiftReg11 is
port(clk,entree : in std_logic;
q8 : out std_logic_vector(7 downto 0));
end ShiftReg11;
architecture aShiftReg11 of ShiftReg11 is
signal dataq : std_logic_vector(10 downto 0);
begin
process(clk) begin
if clk'event and clk='0' then
dataq <= entree & dataq(10 downto 1);
end if;
end process;
q8<=dataq(8 downto 1);
end aShiftReg11;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity comb is
port(
entrees : in std_logic_vector(7 downto 0);
entree : in std_logic;
F : out std_logic);
end comb;
architecture acomb of comb is -- grosse astuce ici pour eliminer aleats
begin
with entree select
F <= entrees(7) and entrees(6) and entrees(5) and entrees(4) and entrees(3) and entrees(2)
and entrees(1) and entrees(0) when '0',
entrees(7) or entrees(6) or entrees(5) or entrees(4) or entrees(3) or entrees(2)
or entrees(1) or entrees(0) when others;
end acomb;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity dflipflop is
port(
d,clk : in std_logic;
q : out std_logic);
end dflipflop;
architecture adflipflop of dflipflop is
signal sigq : std_logic;
begin
process(clk) begin
if clk'event and clk='1' then
sigq <= d;
end if;
end process;
q <= sigq;
end adflipflop;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity seq is
port(
clk, cmpt_is_11,init : in std_logic;
reset,transfert : out std_logic
);
end seq;
architecture aseq of seq is
signal etape0,etape1,etape2 : std_logic;
begin
process(clk) begin
if clk'event and clk = '1' then
etape0 <= etape2 or (etape0 and not cmpt_is_11) or init;
etape1 <= etape0 and cmpt_is_11 and not init;
etape2 <= etape1 and not init;
end if;
end process;
transfert <= etape1;
reset <= etape2;
end aseq;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity compare is
port (
entrees : in std_logic_vector(7 downto 0);
s_cmpt_is_11 : out std_logic
);
end compare;
architecture acompare of compare is begin
with entrees select
s_cmpt_is_11 <= '1' when x"0B",
'0' when others;
end acompare;
auquel on ajoute l'entité et l'architecture du TP 1.
Pour le fichier ucf, peu de modifications :
#horloge générale net "Clk_T9" loc="T9"; #signaux PS/2 net "PS2_Clk_M16" loc="M16"; net "PS2_Data_M15" loc="M15"; #reset net "reset_L14" loc="L14"; # 7 segments des afficheurs net "sorties_aff<6>" loc="e14"; net "sorties_aff<5>" loc="g13"; net "sorties_aff<4>" loc="n15"; net "sorties_aff<3>" loc="p15"; net "sorties_aff<2>" loc="r16"; net "sorties_aff<1>" loc="f13"; net "sorties_aff<0>" loc="n16"; # sélection des afficheurs net "aff<1>" loc="G14"; net "aff<0>" loc="D14"; net "aff<2>" loc="F14"; net "aff<3>" loc="E13";
Travail à réaliser sans sa correction
[modifier | modifier le wikicode]La correction donnée ci-dessus ne correspond pas tout à fait au schéma.
Ajout du registre d'affichage
[modifier | modifier le wikicode]La correction donnée fonctionne correctement mais on s'est passé du registre d'affichage.
Exercice
[modifier | modifier le wikicode]On vous demande de créer et d'ajouter ce registre d'affichage.
Ce registre d'affichage n’est pas nécessaire sauf si on a l'intention d'étendre les fonctionnalités de notre lecture des codes. Ceci sera demandé dans la prochaine section.
Gestion des scancodes 16 bits
[modifier | modifier le wikicode]On vous demande de modifier le séquenceur (GRAFCET) pour gérer l'envoi des scancodes sur deux octets ainsi que leurs affichage. Le principe est le suivant :
- ajouter un deuxième registre d'affichage
- modifier le GRAFCET de séquencement
Le principe du GRAFCET est le suivant :
- retirer l'action de reset de la troisième étape
- à partir de la troisième étape, on attend environ 100 ms et on va dans une quatrième étape qui réalise le reset et boucle au début du GRAFCET
- l'autre branche de la troisième étape sera conditionnée par compteur=12 puis on attend compteur=22 pour faire un transfert dans le deuxième registre d'affichage, puis le reset.
Un schéma sera plus parlant :
Dans cette figure on a pris soin de montrer en rouge les changements matériels auxquels il faut ajouter naturellement le GRAFCET. Dans le GRAFCET il faut utiliser un timer. Le timer est laissé sous la forme d'une variable "t" dans le GRAFCET. On a déjà dit qu’il est bon de choisir une valeur proche de 100 ms. Cela sous-entend donc que si un deuxième octet est envoyé par le clavier ce sera fait en moins de 100 ms.
Nous montrons dans la solution partielle ci-dessous comment il est possible de réaliser cela.
Évidemment le composant de comparaison est à changer :
component compare
port (
entrees : in std_logic_vector(7 downto 0);
s_cmpt_is_11,s_cmpt_is_12,s_cmpt_is_22 : out std_logic
);
end component;
On vous montre comment éviter de réaliser trop de composants : GRAFCET + TIMER. On peut utiliser la technique à deux process différents dans un même composant :
entity seq is
port(
clk, cmpt_is_11,cmpt_is_12,cmpt_is_22,init : in std_logic;
reset,transfert1, transfert2 : out std_logic
);
end seq;
architecture aseq of seq is
signal etape0,etape1,etape2,etape3,etape4,etape5,etape6 : std_logic;
signal s_t_sup_100ms,en,resetTimer : std_logic;
signal qs : std_logic_vector(18 downto 0);
begin
timer:process(clk,resetTimer) begin
if resetTimer = '1' then
qs <= (others => '0');
elsif clk'event and clk='1' then
if en = '1' then
qs <= qs+1;
end if;
end if;
end process;
s_t_sup_100ms <= qs(18);
process(clk) begin
if clk'event and clk = '1' then
-- a compléter les équations de récurrences
etape0 <= etape3 or etape6 or (etape0 and not cmpt_is_11) or init;
etape1 <=
etape2 <= (etape1 or (etape2 and not s_t_sup_100ms and not cmpt_is_12)) and not init;
etape3 <=
etape4 <=
etape5 <=
etape6 <=
end if;
end process;
resetTimer <= etape0; -- ou etape1
en <= etape2;
transfert1 <= etape1;
transfert2 <= etape5;
reset <= etape3 or etape6;
end aseq;
Ce sera tout pour nos indications.
Estimez la valeur "t" du timer qui a été choisie dans la solution ci-dessus.
Mise en conformité des horloges
[modifier | modifier le wikicode]Cette section est difficile à comprendre. Même si elle ne fait intervenir que des notions du niveau indiqué, il est conseillé d'avoir du recul sur les notions présentées pour bien assimiler ce qui suit. Cependant, ce contenu n'est pas fondamental et peut être sauté en première lecture.
Nous avons traité dans le chapitre VHDL et machines à états algorithmiques le problème des horloges. Dans ce chapitre (et le précédent) nous avons utilisé sans discernement des horloges sur front montants et des horloges sur fronts descendants parce qu’il est souvent plus facile d’en comprendre le fonctionnement. Disons que cette facilité de compréhension est effective si vous séparez correctement le séquenceur et la partie opérative (ou chemin de données) ce qui n'a pas toujours été fait. Qu'en est-il du filtrage des fronts de l'horloge PS2 par exemple ?
Si l’on veut faire ce travail dans les règles de l'art avec des exigences professionnelles il serait certainement plus judicieux de n'utiliser que des horloges sur front montant.
Un essai rapide m'a permis de constater que changer tous les fronts d'horloge descendants en fonts montants ne perturbait pas le fonctionnement. Autrement dit le travail précédent de ce chapitre fonctionne encore si toutes les sensibilités d'horloges sont changées pour des fronts montants. Pourtant, pour ne prendre aucun risque, nous allons construire un nouveau recepteur PS/2 de A à Z en essayant à toute étape de respecter les règles d'horloges.
Graphe d'états
[modifier | modifier le wikicode]Rappelons que le meilleur moyen de spécifier un montage qui respecte les horloges sont les (en) FSMD (Finite State Machine with Data path) utilisés par les anglo-saxons. Nous, Français, sommes plus habitués au GRAFCET et à sa version simplifiée : le graphe d'états (lire ou relire le chapitre description par graphe d'état du cours de logique séquentielle). La particularité de ce graphe d'états est que les actions seront elles aussi séquentielles et c’est ce fait que leur programmation en VHDL sera parfois délicate.
Nous allons commencer par présenter notre problème sous la forme d'un graphe d'états.
Cette figure est facile à lire et nous l'implanterons telle quelle. Il faut noter cependant qu’il est possible de retirer tous les états qui précèdent les transitions "=1", c'est-à-dire qui se font systématiquement. Comme nous allons utiliser une horloge à 50 MHz nous n'avons pas besoin de faire cette optimisation : cela a l'avantage de simplifier l'écriture VHDL (mais l'inconvénient de consommer un peu plus de puissance car on commute plus de bascules !).
Ce qui peut choquer le lecteur qui a suivi ce que nous avons déjà fait est le n initialisé à 9 alors que nous avions un compteur qui comptait jusqu'à 11 ! Voici les explications :
- quand vous passez de "e0" vers "e1" vous avez eu un front d'horloge ps2 et vous initialisez seulement votre compteur à 9 (cela fait donc 10 fronts)
- la sortie de l’ensemble des états "e5" "e2" "e3" se fait quand n=0 mais en passant par la transition e2 → e3 qui détecte un front supplémentaire... et voila les 11 fronts descendants de l'horloge PS2.
La construction de ce genre de graphe d'états demande donc une certaine expérience : l'initialisation du compteur à 11 serait catastrophique !
Le graphe d'état passe sous silence la détection du front (variable "fall_edge") et nous allons commencer par sa construction.
Réalisation VHDL
[modifier | modifier le wikicode]Nous allons présenter étape par étape la réalisation du graphe d'états de la section précédente. Nous allons commencer par la détection des fronts descendants de l'horloge PS2, puis ajouter le séquenceur pour enfin ajouter à tout cela le chemin de données. Notre entité est :
-- notre entite inspiree de P. P. Chu
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity ps2_rx is
port (
clk, reset: in std_logic;
ps2d, ps2c: in std_logic; -- key data, key clock
rx_en: in std_logic;
rx_done_tick: out std_logic;
dout: out std_logic_vector(7 downto 0)
);
end ps2_rx;
Réalisation de la détection de front
[modifier | modifier le wikicode]Si l’on veut que la détection de front ne dure qu'une période d'horloge, on peut faire comme ci-dessous :
-- encore une fois inspiré de P. P. Chu (voir programme complet plus loin)
process (clk, reset)
begin
if reset='1' then
filter_reg <= (others=>'0');
f_ps2c_reg <= '0';
elsif (clk'event and clk='1') then
filter_reg <= filter_next;
f_ps2c_reg <= f_ps2c_next;
end if;
end process;
-- on remplit sans arret filter_reg
filter_next <= ps2c & filter_reg(7 downto 1);
-- on detecte si remplit de 1 ou de 0
f_ps2c_next <= '1' when filter_reg="11111111" else
'0' when filter_reg="00000000" else
f_ps2c_reg;
-- pret pour la detection du front descendant
fall_edge <= f_ps2c_reg and (not f_ps2c_next);
Cette partie est séquentielle et ne fonctionne que sur des fronts d'horloge montants.
Réalisation du séquenceur
[modifier | modifier le wikicode]Le séquenceur va être réalisé avec deux process. Cette façon de faire a été peu décrite dans ce document mais elle est tellement commune que nous laissons le soin au lecteur de trouver des documents expliquant ce style de programmation.
type statetype is (e0, e1, e2, e3, e4, e5);
signal state_reg, state_next: statetype;
process (clk, reset)
begin
if reset='1' then
state_reg <= e0;
elsif (clk'event and clk='1') then
state_reg <= state_next;
n_reg <= n_next;
b_reg <= b_next;
end if;
end process;
-- next-state logic
process(state_reg,n_reg,b_reg,fall_edge,rx_en,ps2d)
begin
-- pour eviter certains else
state_next <= state_reg;
case state_reg is
when e0 =>
if fall_edge='1' and rx_en='1' then
state_next <= e1;
end if;
when e1 =>
state_next <=e2;
when e2 =>
if fall_edge='1' then
state_next <= e3;
end if;
when e3 =>
if n="0000" then
state_next <= e4;
else
state_next <= e5;
end if;
when e4 =>
state_next <=e0;
when e5 =>
state_next <=e2;
end case;
end process;
Puisque nous ne nous intéressons qu'au séquenceur, nous retirons tout ce qui concerne les actions. Ceci sera ajouté en section suivante.
Réalisation de l’ensemble
[modifier | modifier le wikicode]Dans cette section, nous allons assembler le code des deux sections précédentes à la description du graphe d'états pour en faire un ensemble complet avec six états. Nous présenterons ensuite la version en trois états qui a fortement inspiré ce code VHDL.
-- notre entite inspiree de P. P. Chu
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity ps2_rx is
port (
clk, reset: in std_logic;
ps2d, ps2c: in std_logic; -- key data, key clock
rx_en: in std_logic;
rx_done_tick: out std_logic;
dout: out std_logic_vector(7 downto 0)
);
end ps2_rx;
architecture arch of ps2_rx is
type statetype is (e0, e1, e2, e3, e4, e5);
signal state_reg, state_next: statetype;
signal filter_reg, filter_next:
std_logic_vector(7 downto 0);
signal f_ps2c_reg,f_ps2c_next: std_logic;
signal b_reg, b_next: std_logic_vector(10 downto 0);
signal n_reg,n_next: unsigned(3 downto 0);
signal fall_edge: std_logic;
begin
--=================================================
-- filter and falling edge tick generation for ps2c
--=================================================
process (clk, reset)
begin
if reset='1' then
filter_reg <= (others=>'0');
f_ps2c_reg <= '0';
elsif (clk'event and clk='1') then
filter_reg <= filter_next;
f_ps2c_reg <= f_ps2c_next;
end if;
end process;
filter_next <= ps2c & filter_reg(7 downto 1);
f_ps2c_next <= '1' when filter_reg="11111111" else
'0' when filter_reg="00000000" else
f_ps2c_reg;
fall_edge <= f_ps2c_reg and (not f_ps2c_next);
--=================================================
-- fsmd to extract the 8-bit data
--=================================================
-- registers
process (clk, reset)
begin
if reset='1' then
state_reg <= e0;
n_reg <= (others=>'0');
b_reg <= (others=>'0');
elsif (clk'event and clk='1') then
state_reg <= state_next;
n_reg <= n_next;
b_reg <= b_next;
end if;
end process;
-- next-state logic
process(state_reg,n_reg,b_reg,fall_edge,rx_en,ps2d)
begin
-- pour eviter certains else
rx_done_tick <='0';
state_next <= state_reg;
n_next <= n_reg;
b_next <= b_reg;
case state_reg is
when e0 =>
if fall_edge='1' and rx_en='1' then
state_next <= e1;
end if;
when e1 =>
state_next <=e2;
b_next <= ps2d & b_reg(10 downto 1);
n_next <= "1001";
when e2 =>
if fall_edge='1' then
state_next <= e3;
end if;
when e3 =>
if n_reg = "0000" then
state_next <= e4;
else
state_next <= e5;
end if;
b_next <= ps2d & b_reg(10 downto 1);
when e4 =>
state_next <=e0;
rx_done_tick <='1';
when e5 =>
state_next <=e2;
n_next <= n_reg - 1;
end case;
end process;
-- output
dout <= b_reg(8 downto 1); -- data bits
end arch;
Bien sûr ce code nécessite au minimum l'ajout d'un registre d'affichage et l’affichage sur au moins deux afficheurs sept segments comme cela a été fait au TP1.
Version à trois États de Pong P. Chu
[modifier | modifier le wikicode]Pong P. Chu dans son livre "FPGA prototyping by VHDL examples" propose une version utilisant seulement trois états que nous donnons maintenant pour comparer avec ce que nous avons fait (d'ailleurs très inspiré par son livre). L’idée est en fait assez simple si l’on était capable de noter des actions qui se déroulent seulement quand on quitte les états. Ceux qui nous ont suivi jusque là se disent mais pourquoi donc, n'était-ce pas le cas comme expliqué dans la section glissement sémantique du chapitre VHDL et machines à états algorithmiques. Et bien non ! Pas si l’on reste coincé dans l'étape ! C'est vrai que l'action se réalise au sortir de l'étape mais seulement si elle est suivie par une transition toujours réceptive ("=1"). Autrement elle est aussi réalisée pendant qu'on reste dans l'étape mais avec un front d'horloge de retard. Tout cela est bien compliqué et probablement pas à la portée d'étudiants de niveau 14.
-- Listing 8.1 (Pong. P. Chu "FPGA prototyping by VHDL examples")
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity ps2_rx is
port (
clk, reset: in std_logic;
ps2d, ps2c: in std_logic; -- key data, key clock
rx_en: in std_logic;
rx_done_tick: out std_logic;
dout: out std_logic_vector(7 downto 0)
);
end ps2_rx;
architecture arch of ps2_rx is
type statetype is (idle, dps, load);
signal state_reg, state_next: statetype;
signal filter_reg, filter_next:
std_logic_vector(7 downto 0);
signal f_ps2c_reg,f_ps2c_next: std_logic;
signal b_reg, b_next: std_logic_vector(10 downto 0);
signal n_reg,n_next: unsigned(3 downto 0);
signal fall_edge: std_logic;
begin
--=================================================
-- filter and falling edge tick generation for ps2c
--=================================================
process (clk, reset)
begin
if reset='1' then
filter_reg <= (others=>'0');
f_ps2c_reg <= '0';
elsif (clk'event and clk='1') then
filter_reg <= filter_next;
f_ps2c_reg <= f_ps2c_next;
end if;
end process;
filter_next <= ps2c & filter_reg(7 downto 1);
f_ps2c_next <= '1' when filter_reg="11111111" else
'0' when filter_reg="00000000" else
f_ps2c_reg;
fall_edge <= f_ps2c_reg and (not f_ps2c_next);
--=================================================
-- fsmd to extract the 8-bit data
--=================================================
-- registers
process (clk, reset)
begin
if reset='1' then
state_reg <= idle;
n_reg <= (others=>'0');
b_reg <= (others=>'0');
elsif (clk'event and clk='1') then
state_reg <= state_next;
n_reg <= n_next;
b_reg <= b_next;
end if;
end process;
-- next-state logic
process(state_reg,n_reg,b_reg,fall_edge,rx_en,ps2d)
begin
rx_done_tick <='0';
state_next <= state_reg;
n_next <= n_reg;
b_next <= b_reg;
case state_reg is
when idle =>
if fall_edge='1' and rx_en='1' then
-- shift in start bit
b_next <= ps2d & b_reg(10 downto 1);
n_next <= "1001";
state_next <= dps;
end if;
when dps => -- 8 data + 1 pairty + 1 stop
if fall_edge='1' then
b_next <= ps2d & b_reg(10 downto 1);
if n_reg = 0 then
state_next <=load;
else
n_next <= n_reg - 1;
end if;
end if;
when load =>
-- 1 extra clock to complete the last shift
state_next <= idle;
rx_done_tick <='1';
end case;
end process;
-- output
dout <= b_reg(8 downto 1); -- data bits
end arch;
Et pour finir, un peu de travail.
Travail à réaliser
[modifier | modifier le wikicode]On vous demande d’utiliser une des corrections que l’on vient de donner (à six états ou à trois états) et de l'insérer dans une application comme indiqué dans la figure ci-dessous pour en tester le bon fonctionnement.
L'appui sur la touche 'a' donnera 15 en hexadécimal comme résultat.