Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/TP 3
Objectif : réaliser un échantillonnage de l'horloge PS/2 et le tester en incrémentant un compteur qui affichera le nombre de fronts d'horloge pour chaque appui de touche sur le clavier.
Le protocole PS/2 permet une communication synchrone, bidirectionnelle et bits en série entre un système et un périphérique (couramment clavier, souris ou touchpad de portable). Chaque côté est susceptible d'envoyer des commandes ou des données sur un fil que l’on nommera "data" dans la suite. Tout ceci est synchronisé par une horloge appelée "PS2_clk". Mais un seul côté ne pourra transmettre à la fois puisque la liaison se fait sur le seul fil "data".
C'est le système maître qui alimente le périphérique PS/2. Les signaux Data et PS2_Clk sont bidirectionnels et à sortie collecteur ouvert. Une résistance de 10K ohm environ doit être placée entre ces deux signaux et l'alimentation ce qui garantit un niveau haut hors de toute transaction.
Une bonne implantation du protocole PS/2 nécessite une machine d'état calquée sur les états du bus. Nous n'utiliserons qu'une implantation partielle du protocole PS/2 et par conséquent une machine d'états assez différente mais beaucoup plus simple.
En résumé, voici les chronogrammes temporels du protocole PS/2 lors d'une transmission d'un périphérique (ici un clavier) vers le maître (ici un PC ou un FPGA).
- Puisque c’est le clavier qui envoie de l'information, c’est lui qui réalise l'horloge appelée "PS2_clk" dans le dessin ci-dessus.
- Les données sont envoyées en commençant par le poids faible : D0 puis D1 ... puis D7.
- La transmission commence par un bit de start.
- Une fois les données sur 8 bits envoyées on a une parité et un bit de stop.
Périphérique vers système maître
modifierLe périphérique peut transmettre un octet vers le système à condition que le bus soit préalablement « idle ». L'octet est transmis sous forme série avec l'horloge (impulsions négatives). Le périphérique change les données sur front montant alors que le système est censé les lire pendant que CLK est à 0 (ou sur le front descendant). Nous allons réaliser une telle communication : dans ce TP ainsi que dans le suivant. Le périphérique sera un clavier.
Travail à réaliser
modifierTravail corrigé
modifierIl est risqué d’utiliser directement l'horloge fournie par le clavier pour commander notre système. En effet le signal peut comporter des aléas qui seront interprétés par le système comme des fronts et provoquer ainsi des décalages dans la réception. Pour éviter cela on échantillonne l'horloge PS/2 au rythme d'une horloge système plus rapide. Chaque échantillon est envoyé dans un registre à décalage auquel on ajoute une bascule D qui mémorise l'état dans lequel on se trouve. Le principe est simple. Si on est dans l'état 0 et que tout le registre est à un on passe à l'état un, si on est dans l'état un et que tout le registre est à 0, alors on passe dans l'état 0. Ceci peut être réalisé par le schéma ci-dessous. Pour comprendre le fonctionnement de cet anti-rebond peu conventionnel, vous pouvez résoudre cet exercice.
Programmez l’ensemble des composants ci-dessus. La sortie intéressante pour nous est la sortie Q de la bascule D(notée « Sortie » sur le schéma). Pour tester l’ensemble et son fonctionnement vous prendrez cette sortie Q comme horloge d'un compteur (avec Remise à Zéro). La sortie de ce compteur sera reliée à un afficheur sept segments du TP précédent. Si votre ensemble fonctionne correctement, chaque appui sur une touche clavier devra afficher une valeur 11 (0x0B ). Mais ce n’est pas exactement ce que vous obtiendrez (voir questions).
Voici le schéma global de ce que vous avez à réaliser.
Tous les schémas présenteront le travail à réaliser sous forme fonctionnelle (quelles sont les entrées, quelles sont les sorties ?). Le bloc à réaliser en VHDL sera toujours coloré en bleu clair.
L'entité correspondant au schéma ci-dessus est donc :
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity tp2 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 tp2;
PS2_Clk_M16 correspond à l'horloge PS/2 tandis que Clk_T9 est l'horloge globale (50 Mhz) de la carte.
Les deux signaux sont ajoutés dans le fichiers ucf :
#signaux PS/2 net "PS2_Clk_M16" loc="M16"; #NET "PS2_Clk_M16" CLOCK_DEDICATED_ROUTE = FALSE; net "PS2_Data_M15" loc="M15";
Les composants à implanter seront donc :
component Counter8
port(horloge,reset : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component ShiftReg
port(clk,entree : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component comb
port(
entrees : in std_logic_vector(7 downto 0);
eQ : in std_logic;
F : out std_logic);
end component;
component dflipflop
port(
d,clk : in std_logic;
q : out std_logic);
end component;
component tp1 is -- composant complet du 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;
Pour chacun des composants on aura à réaliser une entité et une architecture. Remarquons que l'architecture de tp1 a été réalisée dans le TP no 1.
Questions
modifierQuelle valeur effective du compteur lisez-vous pour l'appui d'une touche sur le clavier ? Cela correspond-il avec la documentation du clavier qui explique que l'appui d'une touche envoie son scancode tandis que le relâchement d'une touche donne 0xF0 suivi du scancode. Que se passe-t-il si vous appuyez sur une touche flèche ?
L'appui d'une touche normale du clavier doit afficher "21" en hexadécimal, soit 33 en décimal. Comme indiqué en TP 3, cet appui correspond à un octet envoyé soit 11 fronts d'horloge, puis deux octets pour le relâchement soit en tout 33 fronts d'horloge. Les touches de flèches nous donnent un nombre hexadécimal de 37 soit un nombre décimal de 55 (envoi de 5 octets : deux pour l'appui et 3 pour le relâchement).
Voici sans autres explications la solution du TP2 :
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity tp2 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 tp2;
architecture atp2 of tp2 is
component Counter8 -- compteur sur {{Unité|8|bits}} avec reset asynchrone
port(horloge,reset : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component ShiftReg -- registre à décalage
port(clk,entree : in std_logic;
q : out std_logic_vector(7 downto 0));
end component;
component comb
port(
entrees : in std_logic_vector(7 downto 0);
entree : in std_logic;
F : out std_logic);
end component;
component dflipflop --bascuke D
port(
d,clk : in std_logic;
q : out std_logic);
end component;
component tp1 is -- composant du 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;
signal sig_q8 : std_logic_vector(7 downto 0);
signal sig_q,sig_d : std_logic;
signal sigAAfficher : std_logic_vector(7 downto 0);
begin
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,q=>sigAAfficher);
i5:tp1 port map(clk=>Clk_T9,entrees=>sigAAfficher,s7segs=>sorties_aff,
aff=>aff);
end atp2;
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 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
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;
auquel on ajoutera l'entité et l'architecture du TP1.
Et puis voici le fichier ucf maintenant :
#horloge générale net "Clk_T9" loc="T9"; #horloge PS/2 et Data 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 non corrigé
modifierModifier l'exemple corrigé ci-dessus pour prendre en compte l’affichage sur 4 digits réalisé en fin de TP 1. Tapez un texte sur le clavier pour voir le compteur s'incrémenter petit à petit pour passer progressivement sur 2 digits puis sur trois et enfin sur quatre. Pour cela, il faudra naturellement passer le compteur sur 16 bits.