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

9

Antecedentes

Este es un proyecto personal; se trata de conectar 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! Desafortunadamente, en momentos aleatorios, el dispositivo fallará y luego se recuperará. A través de la depuración, he logrado encontrar el problema, sin embargo, estoy desconcertado sobre 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 no pueda 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 que registran 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 alcanzo los 9 bits, el FPGA comienza a transmitir a través de 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. Durante la duración de la recepción, mi FPGA "echos" la señal de la entrada para 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 FPGA pasa a BAJA inmediatamente después de que finaliza el N64.

Aquí hay un ejemplo de falla:

ingrese la descripción de la imagen aquí

¡Observe que el contador omite los bits 2 y 7! El FPGA llega al final, esperando el próximo bit de inicio del N64 pero nada. Entonces el FPGA agota el tiempo de espera 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 estado finito?

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'. Ver la imagen a continuación: ingrese la descripción de la imagen aquí

Nick Williams
fuente
1
En su primer bloque de casos, está la línea "s_bitCount <= X" 0 ";" ¿Es eso X un error tipográfico?
travisbartley
@ trav1s No, esa "X" denota hexadecimal. Entonces X "0" es en realidad "0000" en binario.
Nick Williams el
1
Recibí un par de errores al ejecutar el código a través de un linter. Las señales N64RXD y tdre no deben usarse en la lista de sensibilidad del proceso secuencial, línea 36.
travisbartley
1
@ trav1s Gracias por el puntero, eliminé esos parámetros; tienes razón, esos no son necesarios. Todavía tengo el problema desafortunadamente. Con el alcance, agregué señales para detectar en qué estado estoy. Por alguna razón, el FPGA salta de "waitForStart" a "waitForStop" sin ningún estado intermedio. Es por eso que no cuenta porque el FPGA no alcanza el estado donde cuenta el bit. El "salto hacia atrás" parece ser el problema.
Nick Williams el
1
Pero la transición "waitForStart" -> "waitForStop" no es válida. No hay forma de hacer ese salto en un solo ciclo. Verifique detenidamente para asegurarse de que no haya un estado muy breve en el medio. De lo contrario, debe haber una falla de hardware / sincronización.
travisbartley

Respuestas:

9

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 relacionados entre sí.

Las señales tardan en propagarse a través del tejido FPGA. La red de reloj dentro del FPGA está diseñada para compensar estos retrasos de "viaje" para 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 su lugar 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 momentos de tiempo se conocen como los tiempos de configuración y retención para un flip flop dado. El componente de ubicación 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.

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 se encuentra en una 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, el circuito lógico digital es, en su nivel más básico, un circuito analógico. Cuando su línea de reloj sube, 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 supera 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 utiliza una cadena de chanclas para capturar la entrada. Cualquier nivel metaestable que podría haber sido capturado por el primer flip flop y logrado lograrlo 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 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 botones, señales UART 'rx' y 'cts' ... cualquier cosa que no esté sincronizada con el reloj que el FPGA está usando para muestrear la señal.

(Un comentario aparte: escribí la página en www.mixdown.ca/n64dev hace muchos años. Me di 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!)

akohlsmith
fuente
¡Gracias por la excelente y completa respuesta! Voy a probar esto y hacer que mi máquina sea más robusta.
Nick Williams
2
En realidad, tiene muy poco que ver con la metaestabilidad (aunque eso también es una preocupación), y todo lo que tiene que ver con los diferentes retrasos de ruta desde la entrada asincrónica a los diversos FF que contienen los bits de la variable de estado.
Dave Tweed
Tienes razón, @DaveTweed; Tiendo a unir los dos juntos y eso es un pensamiento equivocado.
akohlsmith
He editado mi respuesta para tener en cuenta los comentarios de @ DaveTweed.
akohlsmith
1
@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 mezcla; Encontré un montón de recursos en el protocolo N64 que hacía referencia a ese artículo y me decepcionó que el enlace se rompiera. Gracias por arreglarlo.
Nick Williams
6

Su problema es que está utilizando señales no sincronizadas para tomar decisiones en su máquina de estados. 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 lo hace. Esto lo lleva a un estado diferente del previsto, y puede o no ser un estado legal.

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

Dave Tweed
fuente
Sí, esta fue la conclusión a la que estaba a punto de aislar, pero yo no quería saltar a él antes de obtener suficiente información ...
travisbartley
1
Maldición, me ganaste. Lo culpo por escribir todo esto en una tableta. :-)
akohlsmith
@akohlsmith, ser el arma más rápida del este no es lo único que importa para responder. Tu respuesta es útil, y obviamente no fue trampa ya que publicaste tan pronto después de esta.
travisbartley
Solía ​​pensar que when others =>estaba ayudando, pero resulta que no le da lo que dice (bajo cualquier sintetizador que haya usado) a menos que agregue atributos para asegurarse de que el sintetizador comprende que quiere una máquina de estado "segura". El comportamiento normal es optimizar una representación única y no proporcionar lógica de recuperación. Ver xilinx.com/support/answers/40093.html y synopsys.com/Company/Publications/SynopsysInsight/Pages/... por ejemplo.
Martin Thompson
¡Guauu! Ese es un gran consejo y funcionó a las mil maravillas.
Nick Williams