As I'm sure everyone here knows, in FPGA/CPLD design one often needs to synchronize a slower asynchronous signal (say, the SCK line of SPI) with a much faster clock signal thats directly fed to the FPGA/CPLD. My question is, how much faster does the FPGA/CPLD clock needs to be relative to my asynchronous signal? Ten times? Twenty times?
In my case less than 10x doesn't work well. Specifically: I set my SCK speed to 4 MHz whereas my clock was 20 MHz. This didn't work at all. 2 Mhz works, but occasionally I get some problems. At 1 MHz, it works very well - no issues so far.
VHDL Code for the 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;
And here's the code for the sync component:
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;
Regarding simplicity, I know SPI is super simple but I'm a newbie so all of this is rather difficult for me. Only after weeks did it make sense to me that I do need to sync. the signals in the CPLD/FPGA (initially I was just using the SCK as my clock and didn't even have a separate clock on my board. It worked fine for slower speeds but increasing the speed to even 1 MHz made the naiveness of my approach obvious). I'm sure (infact, I know because of your excellent posts around here) your approach is much simpler and more elegant, the issue is that I'll need to get my head around it first because as of now it just sounds like greek to me!