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:
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:
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:
Respostas:
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:
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!)
fonte
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.fonte
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.