Electronic – How fast should I clock the CPLD as compared to the SPI bus’ speed

clockfpgavhdl

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!

Best Answer

There are so many things in this question that it is difficult to know where to start.

I am assuming that your FPGA logic is a SPI slave, not a master. If it is a master then you have a whole different set of issues which I'm going to avoid going into right now.

The simple direct answer to your question is that you need to sample an async signal at least two times the frequency of your signal. So if you have a 4 MHz clock then you need to sample it at 8 MHz or higher. Of course, nothing is simple or direct in this case.

You have things a little more difficult because you are not sampling one async signal, you are sampling three (CLK, CS, and MOSI). You also need to keep those three signals time-aligned with each other through the sampling process. And you have to spit out MISO in such a way as to not violate your setup/hold time at the master.

None of this is easy, but having a higher speed clock will make things much easier. How much higher depends on your code, and you didn't post your code. I think that I could write code to do it with an 8x clock, but that is just a guess. Honestly, however, I think this is the wrong approach.

SPI is a super simple interface, and it would be good if you kept it super simple. SPI has its own clock, and if you use it as a clock then everything becomes almost easy. Instead of changing clock domains on the serial SPI interface, change clock domains on the parallel data going in/out of your shift registers. If you look at those signals carefully you might even realize that you don't need to do anything special, or if you do then it's just a flip-flop per signal. Then you don't need to have your main clock be higher than your SPI clock. Your main clock could actually be slower!

I do this on my SPI FPGA/CPLD interfaces and I have no problems running SPI at 30+ MHz, with or without a second clock domain.