VHDL: el módulo de recepción falla aleatoriamente cuando se cuentan los bits

9

Background

Este es un proyecto personal; Con respecto a la conexión de un FPGA a un N64, los valores de bytes que recibe el FPGA se envían a través de UART a mi computadora. En realidad funciona bastante bien! En momentos aleatorios, desafortunadamente, el dispositivo fallará y luego se recuperará. A través de la depuración, me las arreglé para encontrar el problema, sin embargo, no sé cómo solucionarlo porque soy bastante incompetente con VHDL.

He estado jugando con la VHDL por un par de días y puedo ser incapaz de resolver esto.

El problema

Tengo un osciloscopio que mide la señal N64 en el FPGA, y el otro canal se conecta a la salida del FPGA. También tengo pines digitales grabando el valor del contador.

Esencialmente, el N64 envía 9 bits de datos, incluido un bit STOP. El contador cuenta los bits de datos recibidos y cuando llego a 9 bits, el FPGA comienza a transmitir a través de UART.

Aquí está el comportamiento correcto:

ElFPGAeslaformadeondaazulylaformadeondanaranjaeslaentradadelN64.Durantelarecepción,miFPGA"hace eco" de la señal de la entrada para fines de depuración. Después de que el FPGA cuente hasta 9, comienza a transmitir los datos a través de UART. Observe que los pines digitales cuentan hasta 9 y la salida FPGA se BAJA inmediatamente después de que se termina el N64.

Aquí hay un ejemplo de un error:

¡Notequeelcontadorsaltalosbits2y7!ElFPGAllegaalfinal,esperandoelsiguientebitdeiniciodelN64peronada.AsíqueelFPGAseapagayserecupera.

EsteeselVHDLparaelmóduloderecepciónN64.Contieneelcontador:s_bitCount.

libraryIEEE;useIEEE.STD_LOGIC_1164.all;useIEEE.STD_LOGIC_UNSIGNED.ALL;entityN64RXisport(N64RXD:inSTD_LOGIC;--Datainputclk25:inSTD_LOGIC;clr:inSTD_LOGIC;tdre:inSTD_LOGIC;--detectswhenUARTisreadytransmit:outSTD_LOGIC;--SignaltoUARTtotransmitsel:outSTD_LOGIC;echoSig:outSTD_LOGIC;bitcount:outSTD_LOGIC_VECTOR(3downto0);data:outSTD_LOGIC_VECTOR(3downto0)--Thesignificantnibble);endN64RX;--}}EndofautomaticallymaintainedsectionarchitectureN64RXofN64RXistypestate_typeis(start,delay2us,sigSample,waitForStop,waitForStart,timeout,count9bits,sendToUART);signalstate:state_type;signals_sel,s_echoSig,s_timeoutDetect:STD_LOGIC;signals_baudCount:STD_LOGIC_VECTOR(6downto0);--Countingvariableforbaudrateindelaysignals_bitCount:STD_LOGIC_VECTOR(3downto0);--Countingvariablefornumberofbitsrecievedsignals_data:STD_LOGIC_VECTOR(8downto0);--Signalfordataconstantdelay:STD_LOGIC_VECTOR(6downto0):="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;

Entonces, ¿alguna idea? Consejos de depuración? ¿Consejos para codificar máquinas de estados finitos?

¡Mientras tanto, seguiré jugando con él (lo tendré eventualmente)! ¡Ayúdame a Stack Exchange, eres mi única esperanza!

Editar

Un descubrimiento adicional en mi depuración, los estados pasarán de waitForStart a waitForStop. Le di a cada estado un valor con waitForStart igual a '5' y waitForStop igual a '4'. Vea la imagen de abajo:

    
pregunta Nick Williams

2 respuestas

9

No veo un sincronizador en la línea de datos de rx.

Todas las entradas asíncronas deben estar sincronizadas con el reloj de muestreo. Hay un par de razones para esto: metastabilidad y enrutamiento. Estos son problemas diferentes pero están relacionados entre sí.

Toma tiempo para que las señales se propaguen a través del tejido FPGA. La red de reloj dentro del FPGA está diseñada para compensar estos retrasos de "viaje", de modo que todos los flip flops dentro del FPGA vean el reloj exactamente en el mismo momento. La red de enrutamiento normal no tiene esto, y en cambio se basa en la regla de que todas las señales deben permanecer estables durante un poco de tiempo antes de que cambie el reloj y permanecer estables durante un poco de tiempo después de que el reloj cambie. Estos pequeños bits de tiempo se conocen como los tiempos de configuración y de espera para un flip flop determinado. El componente de lugar y ruta de la cadena de herramientas tiene una muy buena comprensión de los retrasos de enrutamiento para el dispositivo específico y hace una suposición básica de que una señal no viola los tiempos de configuración y retención de los flip flops en el FPGA. Con esa suposición y conocimiento (y un archivo de restricciones de tiempo) puede ubicar correctamente la lógica dentro del FPGA y garantizar que toda la lógica que mira una señal dada ve el mismo valor en cada tic del reloj.

Cuando tiene señales que no están sincronizadas con el reloj de muestreo, puede terminar en una situación en la que un flip flop ve el valor "antiguo" de una señal ya que el nuevo valor no ha tenido tiempo de propagarse. Ahora estás en una situación indeseable en la que la lógica que mira la misma señal ve dos valores diferentes. Esto puede causar un funcionamiento incorrecto, máquinas de estado colapsadas y todo tipo de problemas difíciles de diagnosticar estragos.

La otra razón por la que debes sincronizar todas tus señales de entrada es algo que se llama metastabilidad. Hay volúmenes escritos sobre este tema, pero en pocas palabras, los circuitos lógicos digitales son, en su nivel más básico, un circuito analógico. Cuando su línea de reloj aumenta, se captura el estado de la línea de entrada y si esa entrada no es un nivel alto o bajo estable en ese momento, el flip-flop de muestreo puede capturar un valor "intermedio" desconocido.

Como saben, los FPGA son bestias digitales y no reaccionan bien a una señal que no es alta ni baja. Peor aún, si ese valor indeterminado se abre paso más allá del flip flop de muestreo y entra en el FPGA, puede causar todo tipo de rarezas, ya que porciones más grandes de la lógica ahora ven un valor indeterminado y tratan de darle sentido.

La solución es sincronizar la señal. En su nivel más básico, esto significa que utiliza una cadena de chanclas para capturar la entrada. Cualquier nivel metaestable que pudiera haber sido capturado por el primer flip flop y logrado lograrlo tiene otra oportunidad de resolverse antes de que llegue a tu lógica compleja. Dos flip flops son generalmente más que suficientes para sincronizar entradas.

Un sincronizador básico se ve así:

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 el pin físico para la línea de datos de rx del controlador N64 a la entrada async_in del sincronizador, y conecte la señal sync_out a la entrada de rxd de su UART.

Las señales no sincronizadas pueden causar problemas raros . Asegúrese de que cualquier entrada conectada a un elemento FPGA que no esté sincronizado con el reloj del proceso de lectura de la señal esté sincronizada. Esto incluye pulsadores, señales UART 'rx' y 'cts' ... cualquier cosa que no esté sincronizada con el reloj que está utilizando el FPGA para muestrear la señal.

(Aparte de eso: escribí la página en www.mixdown.ca/n64dev hace muchos años. Me di cuenta de que rompí el haga un enlace la última vez que actualicé el sitio y lo arreglaré por la mañana cuando vuelva a la computadora. No tenía idea de que tanta gente haya usado esa página.

    
respondido por el akohlsmith
6

Su problema es que está utilizando señales no sincronizadas para tomar decisiones en su máquina de estado. Debe alimentar todas esas señales externas a través de sincronizadores de doble FF antes de usarlas en la máquina de estado.

Es un problema sutil con las máquinas de estado que puede surgir en cualquier transición de estado que implique un cambio a dos o más bits en la variable de estado. Si usa una entrada no sincronizada, uno de los bits puede cambiar mientras que el otro no cambia. Esto lo lleva a un estado diferente del previsto, y puede o no ser un estado legal.

La última declaración es la razón por la que también debería tener siempre un caso predeterminado (en VHDL, when others => ... ) en su declaración de caso de máquina de estado que lo lleva de cualquier estado ilegal a uno legal.

    
respondido por el Dave Tweed

Lea otras preguntas en las etiquetas