VHDL: le module de réception échoue de manière aléatoire lors du comptage des bits

9

Contexte

Ceci est un projet personnel; en ce qui concerne la connexion d'un FPGA à un N64, les valeurs d'octets que le FPGA reçoit sont ensuite envoyées via UART à mon ordinateur. Il fonctionne plutôt bien! À des moments aléatoires, malheureusement, l'appareil échouera, puis récupérera. Grâce au débogage, j'ai réussi à trouver le problème, mais je suis perplexe sur la façon de le résoudre car je suis assez incompétent avec VHDL.

Je joue avec le VHDL depuis quelques jours et je suis peut-être incapable de résoudre ce problème.

Le problème

J'ai un oscilloscope mesurant le signal N64 dans le FPGA, et l'autre canal se connecte à la sortie du FPGA. J'ai également des broches numériques enregistrant la valeur du compteur.

Essentiellement, le N64 envoie 9 bits de données, dont un bit STOP. Le compteur compte les bits de données reçus et lorsque j'atteins 9 bits, le FPGA commence à transmettre via UART.

Voici le comportement correct: entrez la description de l'image ici

Le FPGA est la forme d'onde bleue et la forme d'onde orange est l'entrée du N64. Pendant la durée de réception, mon FPGA "fait écho" le signal de l'entrée à des fins de débogage. Une fois que le FPGA compte jusqu'à 9, il commence à transmettre les données via UART. Notez que les broches numériques comptent jusqu'à 9 et que la sortie FPGA devient FAIBLE immédiatement après la fin du N64.

Voici un exemple d'échec:

entrez la description de l'image ici

Notez que le compteur ignore les bits 2 et 7! Le FPGA atteint la fin, attendant le prochain bit de démarrage du N64 mais rien. Le FPGA expire donc et récupère.

Il s'agit du VHDL pour le module de réception N64. Il contient le compteur: s_bitCount.

library IEEE;
use IEEE.STD_LOGIC_1164.all;   
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity N64RX is
     port(
         N64RXD : in STD_LOGIC;                    --Data input
         clk25 : in STD_LOGIC;
         clr : in STD_LOGIC; 
         tdre : in STD_LOGIC;                      --detects when UART is ready
         transmit : out STD_LOGIC;                 --Signal to UART to transmit  
         sel : out STD_LOGIC; 
         echoSig : out STD_LOGIC;
         bitcount : out STD_LOGIC_VECTOR(3 downto 0);
         data : out STD_LOGIC_VECTOR(3 downto 0)   --The significant nibble
         );
end N64RX;

--}} End of automatically maintained section

architecture N64RX of N64RX is 

type state_type is (start, delay2us, sigSample, waitForStop, waitForStart, timeout, count9bits, sendToUART);

signal state: state_type;
signal s_sel, s_echoSig, s_timeoutDetect : STD_LOGIC;
signal s_baudCount : STD_LOGIC_VECTOR(6 downto 0);  --Counting variable for baud rate in delay
signal s_bitCount : STD_LOGIC_VECTOR(3 downto 0);  --Counting variable for number of bits recieved 
signal s_data : STD_LOGIC_VECTOR(8 downto 0);   --Signal for data

constant delay : STD_LOGIC_VECTOR(6 downto 0) := "0110010";  --Provided 25MHz, 50 cycles is 2us 
constant delayLong : STD_LOGIC_VECTOR(6 downto 0) := "1100100";

begin 

n64RX: process(clk25, N64RXD, clr, tdre)
begin
    if clr = '1' then
        s_timeoutDetect <= '0';
        s_echoSig <= '1';
        s_sel <= '0';
        state <= start;
        s_data <= "000000000";
        transmit <= '0'; 
        s_bitCount <= "0000";
        s_baudCount <= "0000000";  
    elsif (clk25'event and clk25 = '1') then    --on rising edge of clock input
        case state is
            when start =>   
                --s_timeoutDetect <= '0';
                s_sel <= '0';
                transmit <= '0';        --Don't request UART to transfer   
                s_data <= "000000000";
                s_bitCount <= X"0";   
                if N64RXD = '1' then
                    state <= start;
                elsif N64RXD = '0' then     --if Start bit detected
                    state <= delay2us;
                end if;    

            when delay2us =>                 --wait two microseconds to sample
                --s_timeoutDetect <= '0';
                s_sel <= '1';
                s_echoSig <= '0';
                if s_baudCount >= delay then    
                    state <= sigSample;
                else
                    s_baudCount <= s_baudCount + 1;
                    state <= delay2us;
                end if;  

            when sigSample => 
                --s_timeoutDetect <= '1';
                s_echoSig <= N64RXD;
                s_bitCount <= s_bitCount + 1;
                s_baudcount <= "0000000";
                s_data <= s_data(7 downto 0) & N64RXD;      
                state <= waitForStop;   

            when waitForStop => 
                s_echoSig <= N64RXD;
                if N64RXD = '0' then
                    state <= waitForStop;
                elsif N64RXD = '1' then
                    state <= waitForStart;
                end if;   

            when waitForStart => 
                s_echoSig <= '1';
                s_baudCount <= s_baudCount + 1; 
                if N64RXD = '0' then 
                    s_baudCount <= "0000000";
                    state <= delay2us;
                elsif N64RXD = '1' then 
                    if s_baudCount >= delayLong then
                        state <= timeout;
                    elsif s_bitCount >= X"9" then
                        state <= count9bits;
                    else
                        state <= waitForStart;
                    end if;
                end if;     

            when count9bits =>  
                s_sel <= '0';
                if tdre = '0' then
                    state <= count9bits;
                elsif tdre = '1' then
                    state <= sendToUART;
                end if;   

            when sendToUART =>
                transmit <= '1';
                if tdre = '0' then
                    state <= start;
                else
                    state <= sendToUART;
                end if;

            when timeout =>
                --s_timeoutDetect <= '1';
                state <= start;

        end case;   
    end if;
end process n64RX;  
--timeoutDetect <= s_timeoutDetect;
bitcount <= s_bitCount;
echoSig <= s_echoSig;
sel <= s_sel;
data <= s_data(4 downto 1);

end N64RX;

Alors, des idées? Conseils de débogage? Conseils sur le codage des machines à états finis?

En attendant, je continuerai à jouer avec (je l'aurai éventuellement)! Aidez-moi Stack Exchange, vous êtes mon seul espoir!

Éditer

Une autre découverte dans mon débogage, les états passeront de waitForStart à waitForStop. J'ai donné à chaque état une valeur avec waitForStart égal à '5' et waitForStop égal à '4'. Voir l'image ci-dessous: entrez la description de l'image ici

Nick Williams
la source
1
Dans votre premier bloc de cas, il y a la ligne "s_bitCount <= X" 0 ";" Est-ce que X est une faute de frappe?
travisbartley
@ trav1s Non, "X" signifie hexadécimal. Donc X "0" est en fait "0000" en binaire.
Nick Williams
1
J'ai eu quelques erreurs lors de l'exécution du code via un linter. Les signaux N64RXD et tdre ne doivent pas être utilisés dans la liste de sensibilité du processus séquentiel, ligne 36.
travisbartley
1
@ trav1s Merci pour le pointeur, j'ai supprimé ces paramètres; vous avez raison, ce ne sont pas nécessaires. J'ai toujours le problème malheureusement. Avec la portée, j'ai ajouté des signaux pour détecter dans quel état je suis. Pour une raison quelconque, le FPGA saute du "waitForStart" au "waitForStop" sans état entre les deux! C'est pourquoi cela ne compte pas car le FPGA n'atteint pas l'état où il compte le bit. Le «saut en arrière» semble être le problème.
Nick Williams
1
Mais la transition "waitForStart" -> "waitForStop" n'est pas valide. Il n'y a aucun moyen de faire ce saut en un seul cycle. Vérifiez de très près pour vous assurer qu'il n'y a pas un état très bref entre les deux. Sinon, il doit y avoir un défaut matériel / temporel.
travisbartley

Réponses:

9

Je ne vois pas de synchroniseur sur la ligne de données rx.

Toutes les entrées asynchrones doivent être synchronisées avec l'horloge d'échantillonnage. Il y a plusieurs raisons à cela: métastabilité et routage. Ce sont des problèmes différents mais interdépendants.

Il faut du temps pour que les signaux se propagent à travers la structure FPGA. Le réseau d'horloge à l'intérieur du FPGA est conçu pour compenser ces retards de «déplacement» afin que toutes les bascules du FPGA voient l'horloge au même moment exact. Le réseau de routage normal ne dispose pas de cela, et s'appuie à la place sur la règle selon laquelle tous les signaux doivent être stables pendant un peu de temps avant que l'horloge ne change et rester stables pendant un peu de temps après le changement d'horloge. Ces petits morceaux de temps sont connus comme les temps de configuration et de maintien pour une bascule donnée. Le composant lieu et itinéraire de la chaîne d'outils a une très bonne compréhension des retards de routage pour le périphérique spécifique et fait l'hypothèse de base qu'un signal ne viole pas les temps de configuration et de maintien des bascules dans le FPGA.

Lorsque vous avez des signaux qui ne sont pas synchronisés avec l'horloge d'échantillonnage, vous pouvez vous retrouver dans la situation où une bascule voit la "vieille" valeur d'un signal puisque la nouvelle valeur n'a pas eu le temps de se propager. Vous êtes maintenant dans la situation indésirable où la logique regardant le même signal voit deux valeurs différentes. Cela peut provoquer un mauvais fonctionnement, des machines d'état en panne et toutes sortes de ravages difficiles à diagnostiquer.

L'autre raison pour laquelle vous devez synchroniser tous vos signaux d'entrée est ce que l'on appelle la métastabilité. Il existe des volumes écrits sur ce sujet, mais en résumé, les circuits logiques numériques sont à leur niveau le plus élémentaire un circuit analogique. Lorsque votre ligne d'horloge augmente, l'état de la ligne d'entrée est capturé et si cette entrée n'est pas un niveau haut ou bas stable à ce moment, une valeur "intermédiaire" inconnue peut être capturée par la bascule d'échantillonnage.

Comme vous le savez, les FPGA sont des bêtes numériques et ne réagissent pas bien à un signal qui n'est ni haut ni bas. Pire, si cette valeur indéterminée fait son chemin au-delà de la bascule d'échantillonnage et dans le FPGA, elle peut provoquer toutes sortes de bizarreries car de plus grandes parties de la logique voient maintenant une valeur indéterminée et tentent de la comprendre.

La solution est de synchroniser le signal. À son niveau le plus élémentaire, cela signifie que vous utilisez une chaîne de tongs pour capturer l'entrée. Tout niveau métastable qui aurait pu être capturé par la première bascule et réussi à le distinguer obtient une autre chance d'être résolu avant qu'il n'atteigne votre logique complexe. Deux bascules sont généralement plus que suffisantes pour synchroniser les entrées.

Un synchroniseur de base ressemble à ceci:

entity sync_2ff is
port (
    async_in : in std_logic;
    clk : in std_logic;
    rst : in std_logic;
    sync_out : out std_logic
);
end;

architecture a of sync_2ff is
begin

signal ff1, ff2: std_logic;

-- It's nice to let the synthesizer know what you're doing. Altera's way of doing it as follows:
ATTRIBUTE altera_attribute : string;
ATTRIBUTE altera_attribute OF ff1 : signal is "-name SYNCHRONIZER_IDENTIFICATION ""FORCED IF ASYNCHRONOUS""";
ATTRIBUTE altera_attribute OF a : architecture is "-name SDC_STATEMENT ""set_false_path -to *|sync_2ff:*|ff1 """;

-- also set the 'preserve' attribute to ff1 and ff2 so the synthesis tool doesn't optimize them away
ATTRIBUTE preserve: boolean;
ATTRIBUTE preserve OF ff1: signal IS true;
ATTRIBUTE preserve OF ff2: signal IS true;

synchronizer: process(clk, rst)
begin
if rst = '1' then
    ff1 <= '0';
    ff2 <= '0';
else if rising_edge(clk) then
    ff1 <= async_in;
    ff2 <= ff1;
    sync_out <= ff2;
end if;
end process synchronizer;
end sync_2ff;

Connectez la broche physique de la ligne de données rx du contrôleur N64 à l'entrée async_in du synchroniseur et connectez le signal sync_out à l'entrée rxd de votre UART.

Les signaux non synchronisés peuvent provoquer des problèmes étranges . Assurez-vous que toute entrée connectée à un élément FPGA qui n'est pas synchronisé avec l'horloge du processus de lecture du signal est synchronisée. Cela inclut les boutons poussoirs, les signaux UART 'rx' et 'cts' ... tout ce qui n'est pas synchronisé avec l'horloge que le FPGA utilise pour échantillonner le signal.

(Un aparté: j'ai écrit la page à www.mixdown.ca/n64dev il y a de nombreuses années. Je viens de réaliser que j'ai rompu le lien lors de la dernière mise à jour du site et que je le corrigerai le matin lorsque je serai de retour à un ordinateur. Je ne savais pas que tant de gens utilisaient cette page!)

akohlsmith
la source
Merci pour la grande et complète réponse! Je vais essayer et rendre ma machine plus robuste.
Nick Williams
2
En fait, cela a très peu à voir avec la métastabilité (bien que cela soit également une préoccupation), et tout à voir avec les différents retards de chemin de l'entrée asynchrone aux divers FF qui contiennent les bits de la variable d'état.
Dave Tweed
Vous avez raison, @DaveTweed; J'ai tendance à regrouper les deux et c'est une mauvaise pensée.
akohlsmith
J'ai modifié ma réponse pour prendre en compte les commentaires de @ DaveTweed.
akohlsmith
1
@akohlsmith Incroyable! J'ai ajouté le synchroniseur et c'était la solution. C'est aussi une incroyable coïncidence que vous ayez écrit la page de mixage; J'ai trouvé un tas de ressources sur le protocole N64 qui faisait référence à cet article et j'ai été déçu que le lien ait été rompu. Merci de l'avoir réparé.
Nick Williams
6

Votre problème est que vous utilisez des signaux non synchronisés pour prendre des décisions dans votre machine d'état. Vous devez alimenter tous ces signaux externes via des synchroniseurs à double FF avant de les utiliser dans la machine d'état.

C'est un problème subtil avec les machines à états qui peut survenir dans toute transition d'état qui implique un changement de deux bits ou plus dans la variable d'état. Si vous utilisez une entrée non synchronisée, l'un des bits peut changer tandis que l'autre ne change pas. Cela vous amène à un état différent de celui prévu, et il peut ou non être un état légal.

Cette dernière instruction est la raison pour laquelle vous devez également toujours avoir un cas par défaut (en VHDL, when others => ...) dans votre déclaration de cas de machine d'état qui vous fait passer de n'importe quel état illégal à un état légal.

Dave Tweed
la source
Ouais, c'était la conclusion que j'allais isoler, mais je ne voulais pas sauter dessus avant d'avoir suffisamment d'informations ...
travisbartley
1
Merde, tu m'as battu. Je le blâme sur ma saisie de tout cela sur une tablette. :-)
akohlsmith
@akohlsmith, être l'arme la plus rapide de l'est n'est pas la seule chose qui compte pour répondre. Votre réponse est utile et ne triche évidemment pas depuis que vous avez posté si peu de temps après celui-ci.
travisbartley
Je pensais que cela when others =>aidait, mais il s'avère que cela ne vous donne pas ce que vous prétendez (sous n'importe quel synthétiseur que j'ai utilisé) à moins que vous ajoutiez des attributs pour vous assurer que le synthétiseur comprend que vous voulez une machine d'état "sûre". Le comportement normal consiste à optimiser pour une représentation à chaud et à ne pas fournir de logique de récupération. Voir par exemple xilinx.com/support/answers/40093.html et synopsys.com/Company/Publications/SynopsysInsight/Pages/… .
Martin Thompson
Hou la la! C'est une excellente astuce et cela a fonctionné comme un charme.
Nick Williams