Como estoy seguro de que todos aquí saben, en el diseño de FPGA / CPLD, a menudo se necesita sincronizar una señal asíncrona más lenta (por ejemplo, la línea SCK de SPI) con una señal de reloj mucho más rápida que se envía directamente al FPGA / CPLD. Mi pregunta es, ¿cuánto más rápido debe ser el reloj FPGA / CPLD en relación con mi señal asíncrona? ¿Diez veces? ¿Veinte veces?
En mi caso, menos de 10x no funciona bien. Específicamente: configuré mi velocidad de SCK a 4 MHz mientras que mi reloj fue de 20 MHz. Esto no funcionó en absoluto. 2 Mhz funciona, pero ocasionalmente me surgen algunos problemas. A 1 MHz, funciona muy bien, sin problemas hasta ahora.
Código VHDL para el CPLD:
library ieee;
use ieee.std_logic_1164.all;
entity PISO is
port(CLK, nCS, SCK, nRESET : in std_logic;
PI : in std_logic_vector(71 downto 0);
SO : out std_logic);
end PISO;
architecture archi of PISO is
signal tmp: std_logic_vector(PI'high downto PI'low);
signal bitOut: std_logic;
signal rise, fall : std_logic;
signal oscena: std_logic;
signal iCLK : std_logic;
signal SCK_rising, SCK_falling, SCK_sync, SCK_delay : std_logic;
signal CS_rising, CS_falling, CS_sync, CS_delay : std_logic;
component sync
generic(
RESET_STATE : std_logic := '0' -- '0' for active low sync
);
port(
clk : in std_logic;
rstN : in std_logic;
d : in std_logic;
q : out std_logic
);
end component;
begin
sync1 : sync
generic map(
RESET_STATE => '0'
)
port map(
clk => clk,
rstN => nRESET,
d => sck,
q => SCK_sync
);
sync2 : sync
generic map(
RESET_STATE => '1'
)
port map(
clk => clk,
rstN => nRESET,
d => nCS,
q => CS_sync
);
process(clk, nRESET)
begin
if (nRESET = '0') then
sck_rising <= '0';
sck_falling <= '0';
sck_delay <= '0';
elsif rising_edge(clk) then
if cs_sync = '1' then
sck_delay <= '0';
sck_rising <= '0';
sck_falling <= '0';
else
sck_delay <= sck_sync;
sck_rising <= sck_sync and (not sck_delay);
sck_falling <= (not sck_sync) and sck_delay;
end if;
end if;
end process;
process(clk, nRESET)
begin
if (nRESET = '0') then
cs_rising <= '0';
cs_falling <= '0';
cs_delay <= '0';
elsif rising_edge(clk) then
cs_delay <= cs_sync;
cs_rising <= cs_sync and (not cs_delay);
cs_falling <= (not cs_sync) and cs_delay;
end if;
end process;
process(CLK, nRESET)
begin
if (nRESET = '0') then
tmp <= (others => '0');
elsif rising_edge(CLK) then
if CS_sync = '0' then
if SCK_falling = '1' then
tmp <= tmp(PI'high -1 downto PI'low) & '0';
end if;
elsif CS_sync = '1' then
tmp <= PI;
end if;
end if;
end process;
SO <= tmp(PI'high) when nCS = '0' else 'Z';
end archi;
Y aquí está el código para el componente de sincronización:
library ieee;
use ieee.std_logic_1164.all;
entity sync is
generic (
RESET_STATE : std_logic := '0' -- '0' for active low sync
);
port (
clk : in std_logic;
rstN : in std_logic;
d : in std_logic;
q : out std_logic
);
end entity;
architecture behavioral of sync is
signal d_meta : std_logic;
begin
process(clk, rstN)
begin
if (rstN = '0') then
d_meta <= RESET_STATE;
q <= RESET_STATE;
elsif (clk'event and clk = '1') then
d_meta <= d;
q <= d_meta;
end if;
end process;
end architecture;
En cuanto a la simplicidad, sé que SPI es super simple pero soy un novato, por lo que todo esto es bastante difícil para mí. Solo después de semanas tenía sentido para mí que necesitaba sincronizar. las señales en el CPLD / FPGA (inicialmente solo estaba usando el SCK como mi reloj y ni siquiera tenía un reloj separado en mi tablero. Funcionó bien para velocidades más lentas, pero aumentar la velocidad incluso a 1 MHz hizo que la ingenuidad de mi enfoque obvio). Estoy seguro (de hecho, lo sé por sus excelentes publicaciones por aquí) su enfoque es mucho más simple y elegante, el problema es que tendré que entenderlo primero porque a partir de ahora simplemente suena como griego a mi!