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:
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:
¡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:
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!)
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.
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.
travisbartley
nick williams
travisbartley
nick williams
travisbartley
nick williams
travisbartley
travisbartley
nick williams
travisbartley
Aarón D. Marasco
Aarón D. Marasco
nick williams
usuario8352
nick williams
travisbartley
nick williams
nick williams
usuario8352
travisbartley
travisbartley
nick williams
usuario8352