VHDL: el módulo de recepción falla aleatoriamente al contar bits

Fondo

Este es un proyecto personal; se trata de conectar un FPGA a un N64, los valores de byte que recibe el FPGA se envían a través de UART a mi computadora. ¡En realidad funciona bastante bien! Desafortunadamente, en momentos aleatorios, el dispositivo fallará y luego se recuperará. A través de la depuración, logré encontrar el problema, sin embargo, no sé cómo solucionarlo porque soy bastante incompetente con VHDL.

He estado jugando con el VHDL durante un par de días y es posible que sea incapaz de resolver esto.

El problema

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

Esencialmente, el N64 envía 9 bits de datos, incluido un bit de PARADA. El contador cuenta los bits de datos recibidos y cuando llego a 9 bits, la FPGA comienza a transmitir vía UART.

Aquí está el comportamiento correcto:ingrese la descripción de la imagen aquí

El FPGA es la forma de onda azul y la forma de onda naranja es la entrada del N64. Mientras dure la recepción, mi FPGA "hace eco" de la señal de la entrada con fines de depuración. Después de que el FPGA cuenta hasta 9, comienza a transmitir los datos a través de UART. Tenga en cuenta que los pines digitales cuentan hasta 9 y la salida de la FPGA pasa a nivel BAJO inmediatamente después de que finaliza el N64.

Aquí hay un ejemplo de una falla:

ingrese la descripción de la imagen aquí

¡Observe que el contador salta los bits 2 y 7! El FPGA llega al final, esperando el siguiente bit de inicio del N64 pero nada. Entonces el FPGA expira y se recupera.

Este es el VHDL para el módulo de recepción N64. Contiene el 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;

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

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

Editar

Otro descubrimiento en mi depuración, los estados saltará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 a continuación:ingrese la descripción de la imagen aquí

En su primer bloque de casos, está la línea "s_bitCount <= X"0";" ¿Es esa X un error tipográfico?
@ trav1s No, esa "X" denota hexadecimal. Entonces X"0" es en realidad "0000" en binario.
Está bien, soy más una persona de Verilog. No particularmente relacionado con la solución del problema, pero tengo curiosidad por saber qué estás haciendo con el N64. leyendo la rom? ¿Construyendo un emulador?
@ trav1s Entiendo, me enseñaron VHDL, así que me quedo con él. Estoy construyendo un controlador BT N64, con el FPGA como el lado del dongle (no quería explotar con un MCU). Los módulos BT que estoy viendo usan UART, por eso estoy usando ese protocolo.
Si es posible, ¿podría mostrar lo que tiene arriba, pero incluyendo todas las E/S y los registros internos? La cantidad de señales no es demasiado alta y realmente ayudará a rastrear el error.
@ trav1s No es mucho, solo un módulo UART TX y algunos otros módulos muy pequeños. En cuanto a E/S, son solo un par de pines para entrada y salida UART. Ya aislé la causa al componente N64RX.
Ya veo. Parece que hay más de una señal que podría causar este error si su lógica de control es defectuosa. Sabemos que s_bitCount tiene un problema, pero no sabemos en qué parte de la lógica de control está fallando. El principal sospechoso puede ser el registro estatal, pero si pudiéramos rastrear el código mientras observamos todas las señales, el problema sería evidente.
Es posible que sea imposible observar todas las señales en el osciloscopio, por lo que es posible que deba crear una prueba en simulación que reproduzca el error.
@ trav1s Puedo intentar simularlo varias veces, sin embargo, no he visto el problema producido allí. Le daré un giro. ¡Gracias por la información hasta ahora!
Recibí un par de errores al ejecutar el código a través de un linter. Las señales N64RXD y tdre no deben utilizarse en la lista de sensibilidad del proceso secuencial, línea 36.
¿Puedes hacer que el reloj aparezca en la pantalla? No tuve tiempo de leer su código, pero viendo la lentitud con la que reacciona el LSB, creo que está submuestreando. Vi una línea de reloj de 25 MHz, lo que significa que la señal más rápida que puede muestrear es de 12,5 Mbps .
@ trav1s: las listas de sensibilidad son falsas en síntesis. ;)
@ trav1s Gracias por el puntero, eliminé esos parámetros; tienes razón, esos no son necesarios. Todavía tengo el problema desafortunadamente. Con el osciloscopio, agregué señales para detectar en qué estado me encuentro. Por alguna razón, el FPGA salta de "waitForStart" a "waitForStop" ¡sin ningún estado intermedio! Es por eso que no está contando porque el FPGA no llega al estado donde cuenta el bit. El "salto hacia atrás" parece ser el problema.
Lance tdre en su pantalla de forma de onda y vea lo que está haciendo. Pregúntese qué puede interferir con la salida.
@AaronD.Marasco Hola Aaron, la longitud de bits de N64 es 1us frente al reloj de 25MHz. Puedo aumentar el muestreo a 50MHz, pero creo que hay algún problema con mi máquina de estado, puse algunos detalles en mi comentario anterior.
Pero la transición "waitForStart" -> "waitForStop" no es válida. No hay forma de dar ese salto en un solo ciclo. Verifique muy de cerca para asegurarse de que no haya un estado muy breve en el medio. De lo contrario, debe haber una falla de hardware/tiempo.
@DavidKoontz El tdre es una entrada del UART TX para indicarle a mi N64RX cuándo está listo para otra transmisión. Tengo ese UART arrancando a 115200, por lo que tiene mucho espacio para la cabeza. El N64 no hace otra señal durante 1,6 ms y solo estoy enviando un byte.
@ trav1s Estaba igual de sorprendido, no veo que ocurra una transición entre los dos, así que estoy un poco perplejo de cómo sucedió esto.
@NickWilliams Sí, pero ¿de dónde viene tdre? ¿Se filtra el impulso o se deriva del reloj que opera su máquina de estado? Si no está filtrado, podría estar sufriendo de ruido demasiado rápido para ver con su alcance. Imagine un problema de hardware como una posibilidad además de un problema con el diseño.
@DavidKoontz, si tdre se eliminó de la lista de sensibilidad, entonces está persiguiendo una pista falsa. Esa señal simplemente no es relevante en la transición de estado defectuoso.
@NickWilliams, ¿qué otra información tiene sobre la falla? ¿Puedes reproducirlo cada vez, o sucede de forma aleatoria e impredecible?
@ trav1s El "salto hacia atrás" para los estados ocurre cada vez que veo el problema. ¡Publiqué una captura de pantalla en mi respuesta para que sepas que no estoy loco! Gracias por la ayuda hasta ahora.
@travis Integridad de la señal. Nada que ver con las listas de sensibilidad. Piense en problemas de tiempo de configuración para el estado.

Respuestas (2)

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

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

Se necesita tiempo para que las señales se propaguen a través de la estructura FPGA. La red de reloj dentro de la FPGA está diseñada para compensar estos retrasos de "viaje" para que todos los flip flops dentro de la 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 ser estables durante un tiempo antes de que cambie el reloj y permanecer estables durante un tiempo después de que cambie el reloj. Estos pequeños períodos de tiempo se conocen como tiempos de configuración y espera para un flip flop dado. 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 espera de los flip flops en el FPGA.

Cuando tiene señales que no están sincronizadas con el reloj de muestreo, puede terminar en la 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 la situación indeseable donde la lógica que mira la misma señal ve dos valores diferentes. Esto puede causar una operación incorrecta, máquinas de estado bloqueadas y todo tipo de estragos difíciles de diagnosticar.

La otra razón por la que debe sincronizar todas sus señales de entrada es algo llamado metaestabilidad. 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 sabes, los FPGA son bestias digitales y no reaccionan bien a una señal que no es ni alta ni baja. Peor aún, si ese valor indeterminado pasa por el 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 usa una cadena de chanclas para capturar la entrada. Cualquier nivel metaestable que podría haber sido capturado por el primer flip flop y logró salir tiene otra oportunidad de resolverse antes de que llegue a su lógica compleja. Dos flip flops suelen ser más que suficientes para sincronizar las 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 rx del controlador N64 a la entrada async_in del sincronizador y conecte la señal sync_out a la entrada rxd de su UART.

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

(Aparte: escribí la página en www.mixdown.ca/n64dev hace muchos años. Me acabo de dar cuenta de que rompí el enlace la última vez que actualicé el sitio y lo arreglaré en la mañana cuando vuelva a la computadora. ¡No tenía idea de que tanta gente usara esa página!)

¡Gracias por la excelente y completa respuesta! Voy a probar esto y hacer que mi máquina sea más robusta.
En realidad, tiene muy poco que ver con la metaestabilidad (aunque eso también es una preocupación) y tiene mucho que ver con los diferentes retrasos en la ruta desde la entrada asíncrona hasta los diversos FF que contienen los bits de la variable de estado.
Tienes razón, @DaveTweed; Tiendo a agrupar los dos juntos y eso es un pensamiento equivocado.
Edité mi respuesta para tener en cuenta los comentarios de @DaveTweed.
@akohlsmith ¡Increíble! Agregué el sincronizador y fue la solución. Además, es una coincidencia increíble que hayas escrito la página de mezclas; Encontré un montón de recursos en el protocolo N64 que hacían referencia a ese artículo y me decepcionó que el enlace estuviera roto. Gracias por arreglarlo.

Su problema es que está utilizando señales no sincronizadas para tomar decisiones en su máquina de estado. Debería estar alimentando 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 involucre un cambio en 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 al deseado, y puede que sea o no un estado legal.

Esa última declaración es la razón por la que siempre debe tener 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.

Sí, esta era la conclusión que estaba a punto de aislar, pero no quería saltar a ella antes de obtener suficiente información...
Solía ​​​​pensar que eso when others =>estaba ayudando, pero resulta que no obtiene lo que dice (bajo cualquier sintetizador que haya usado) a menos que agregue atributos para asegurarse de que el sintetizador entienda que desea una máquina de estado "segura". El comportamiento normal es optimizar a una representación única y no proporcionar lógica de recuperación. Consulte xilinx.com/support/answers/40093.html y synopsys.com/Company/Publications/SynopsysInsight/Pages/… por ejemplo.
¡Guau! Es un gran consejo y funcionó a las mil maravillas.