VHDL: módulo de recepção falha aleatoriamente ao contar bits


9

fundo

Este é um projeto pessoal; No que diz respeito à conexão de um FPGA a um N64, os valores de bytes recebidos pelo FPGA são enviados pelo UART ao meu computador. Na verdade, funciona muito bem! Infelizmente, em momentos aleatórios, o dispositivo falhará e depois se recuperará. Através da depuração, eu consegui encontrar o problema, mas estou perplexo em saber como corrigi-lo porque sou bastante incompetente com o VHDL.

Estou brincando com o VHDL há alguns dias e posso ser incapaz de resolver isso.

O problema

Eu tenho um osciloscópio medindo o sinal N64 no FPGA, e o outro canal se conecta à saída do FPGA. Eu também tenho pinos digitais gravando o valor do contador.

Essencialmente, o N64 envia 9 bits de dados, incluindo um bit STOP. O contador conta os bits de dados recebidos e, quando chego aos 9 bits, o FPGA começa a transmitir via UART.

Aqui está o comportamento correto: insira a descrição da imagem aqui

O FPGA é a forma de onda azul e a forma de onda laranja é a entrada do N64. Durante o recebimento, meu FPGA "repete" o sinal da entrada para fins de depuração. Depois que o FPGA conta até 9, ele começa a transmitir os dados através do UART. Observe que os pinos digitais contam até 9 e a saída do FPGA fica LOW imediatamente após a finalização do N64.

Aqui está um exemplo de falha:

insira a descrição da imagem aqui

Observe que o contador ignora os bits 2 e 7! O FPGA chega ao fim, aguardando o próximo bit de partida do N64, mas nada. Então o FPGA expira e se recupera.

Este é o VHDL para o módulo de recebimento N64. Ele contém o contador: 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;

Então, alguma ideia? Dicas de depuração? Dicas para codificar máquinas de estados finitos?

Enquanto isso, continuarei brincando com ele (acabarei com ele)! Ajude-me a Stack Exchange, você é minha única esperança!

Editar

Uma descoberta adicional na minha depuração, os estados passarão de waitForStart de volta para waitForStop. Eu dei a cada estado um valor com waitForStart igual a '5' e waitForStop igual a '4'. Veja a imagem abaixo: insira a descrição da imagem aqui


11
No seu primeiro bloco de casos, existe a linha "s_bitCount <= X" 0 ";" Esse X é um erro de digitação?
Travisartley

@ trav1s Não, esse "X" indica hexadecimal. Então X "0" é realmente "0000" em binário.
Nick Williams

11
Eu recebi alguns erros ao executar o código através de um linter. Sinais N64RXD e tdre não deve ser utilizado na lista de sensibilidade do processo sequencial, linha 36.
travisbartley

11
@ trav1s Obrigado pelo ponteiro, removi esses parâmetros; você está certo, isso não é necessário. Ainda tenho o problema, infelizmente. Com o escopo, adicionei sinais para detectar em que estado estou. Por alguma razão, o FPGA salta do "waitForStart" de volta para o "waitForStop" sem estado intermediário! É por isso que não está contando porque o FPGA não atinge o estado em que conta o bit. O "salto para trás" parece ser o problema.
Nick Williams

11
Mas a transição "waitForStart" -> "waitForStop" é inválida. Não há como fazer esse salto em um único ciclo. Verifique muito de perto para garantir que não haja um estado muito breve no meio. Caso contrário, deve haver uma falha de hardware / tempo.
Travisartley

Respostas:


9

Não vejo um sincronizador na linha de dados rx.

Todas as entradas assíncronas devem ser sincronizadas com o relógio de amostragem. Existem algumas razões para isso: metaestabilidade e roteamento. Estes são problemas diferentes, mas estão inter-relacionados.

Leva tempo para os sinais se propagarem através da malha FPGA. A rede de relógio dentro do FPGA foi projetada para compensar esses atrasos de "viagem", para que todos os chinelos dentro do FPGA vejam o relógio exatamente no mesmo momento. A rede de roteamento normal não possui isso e, em vez disso, conta com a regra de que todos os sinais devem ser estáveis ​​por um pouco de tempo antes que o relógio mude e permaneçam estáveis ​​por um pouco de tempo após o relógio mudar. Esses pequenos momentos são conhecidos como tempos de configuração e espera para um determinado flip-flop. O componente local e de rota da cadeia de ferramentas tem um entendimento muito bom dos atrasos de roteamento para o dispositivo específico e supõe que um sinal não viola a configuração e os tempos de espera dos chinelos no FPGA.

Quando você tem sinais que não são sincronizados com o relógio de amostragem, pode acabar na situação em que um flip-flop vê o valor "antigo" de um sinal, pois o novo valor não teve tempo de se propagar. Agora você está na situação indesejável em que a lógica que olha para o mesmo sinal vê dois valores diferentes. Isso pode causar operação incorreta, máquinas de estado com falha e todo tipo de diagnóstico difícil.

A outra razão pela qual você deve sincronizar todos os seus sinais de entrada é algo chamado metaestabilidade. Existem volumes escritos sobre esse assunto, mas, em poucas palavras, o circuito lógico digital é, no seu nível mais básico, um circuito analógico. Quando sua linha do relógio aumenta, o estado da linha de entrada é capturado e se essa entrada não estiver em um nível alto ou baixo estável naquele momento, um valor desconhecido "intermediário" poderá ser capturado pelo flip-flop de amostragem.

Como você sabe, os FPGAs são bestas digitais e não reagem bem a um sinal que não é alto nem baixo. Pior ainda, se esse valor indeterminado passar do flip-flop de amostragem e entrar no FPGA, poderá causar todo tipo de estranheza, pois partes maiores da lógica agora veem um valor indeterminado e tentam entendê-lo.

A solução é sincronizar o sinal. No nível mais básico, isso significa que você usa uma cadeia de chinelos para capturar a entrada. Qualquer nível metaestável que possa ter sido capturado pelo primeiro flip-flop e conseguido sair tem outra chance de ser resolvido antes que atinja sua lógica complexa. Dois chinelos são geralmente mais do que suficientes para sincronizar as entradas.

Um sincronizador básico é assim:

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;

Conecte o pino físico da linha de dados rx do controlador N64 à entrada async_in do sincronizador e conecte o sinal sync_out à entrada rxd da sua UART.

Sinais não sincronizados podem causar problemas estranhos . Certifique-se de que qualquer entrada conectada a um elemento FPGA que não esteja sincronizado com o relógio do processo que está lendo o sinal esteja sincronizada. Isso inclui botões, sinais UART 'rx' e 'cts' ... qualquer coisa que não seja sincronizada com o relógio que o FPGA está usando para amostrar o sinal.

(Além disso: escrevi a página em www.mixdown.ca/n64dev há muitos anos. Acabei de perceber que quebrei o link quando atualizei o site pela última vez e o corrigirei pela manhã quando voltar ao computador. Eu não fazia ideia de que tantas pessoas usavam essa página!)


Obrigado pela resposta excelente e abrangente! Vou tentar e tornar minha máquina mais robusta.
Nick Williams

2
Na verdade, ele tem muito pouco a ver com metaestabilidade (embora isso também seja uma preocupação) e tudo a ver com os atrasos dos diferentes caminhos, da entrada assíncrona aos vários FFs que contêm os bits da variável de estado.
Dave Tweed

Você está certo, @DaveTweed; Eu costumo agrupar os dois e isso é um pensamento errado.
akohlsmith

Editei minha resposta para levar em conta os comentários de @ DaveTweed.
Akohlsmith

11
@akohlsmith Amazing! Eu adicionei o sincronizador e foi a solução. Além disso, é uma coincidência incrível que você tenha escrito a página de mixagem; Encontrei vários recursos no protocolo N64 que referenciavam esse artigo e fiquei desapontado por o link estar quebrado. Obrigado por corrigi-lo.
Nick Williams

6

Seu problema é que você está usando sinais não sincronizados para tomar decisões em sua máquina de estado. Você deve alimentar todos esses sinais externos por meio de sincronizadores FF duplos antes de usá-los na máquina de estado.

É um problema sutil com máquinas de estado que pode surgir em qualquer transição de estado que envolva uma alteração em dois ou mais bits na variável de estado. Se você usar uma entrada não sincronizada, um dos bits pode mudar enquanto o outro falha na alteração. Isso leva você a um estado diferente do pretendido e pode ou não ser um estado legal.

Essa última afirmação é a razão pela qual você também deve sempre ter um caso padrão (em VHDL when others => ...) na declaração de caso da máquina de estado que o leva de qualquer estado ilegal para legal.


Sim, esta foi a conclusão que eu estava prestes a isolado, mas eu não queria saltar para ele antes de começar informação suficiente ...
travisbartley

11
Droga, você me venceu. Eu culpo a minha digitação de tudo isso em um tablet. :-)
akohlsmith 23/08

@akohlsmith, ser a arma mais rápida do leste não é a única coisa que importa em responder. Sua resposta é útil e, obviamente, não foi trapaça desde que você postou logo após esta.
Travisartley

Eu achava que isso when others =>estava ajudando, mas acontece que não recebe o que você reivindica (em qualquer sintetizador que usei), a menos que você adicione atributos para garantir que o sintetizador entenda que você deseja uma máquina de estado "segura". O comportamento normal é otimizar para uma representação quente e não fornecer lógica de recuperação. Consulte xilinx.com/support/answers/40093.html e synopsys.com/Company/Publications/SynopsysInsight/Pages/… por exemplo.
Martin Thompson

Uau! Essa é uma ótima dica e funcionou como um encanto.
Nick Williams
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.