Programmable Devices
CPLDs, FPGAs, SoC FPGAs, Configuration, and Transceivers
20641 Discussions

Designing a SPI slave

Altera_Forum
Honored Contributor II
3,131 Views

Hello everyone,  

 

This is my 1st post on this forum, so I apologize if I'm doing some noob mistake, i.e. posting this question in the wrong place. I remember doing that in other forums :) 

 

Anyways, I'm trying to add a SPI slave to my Cyclone IV VHDL design. It works, but looks like the SPI_CLK pin is very often clocking twice on a single rising edge. An ATXMEGA MCU is generating the signals as the master. 

 

First thought was the speed of the rising slope, and so I shortened the wires on my prototype to reduce the load. It helped. The amount of double clocks reduced. But it's still happening, and the wires are pretty short already. 

 

My question is: Should I be using a dedicated clock pin of the FPGA for the SPI_CLK? I've read that those dedicated clock pins indeed offer a lower load to the signal, but their main feature is being global and easily accessible to all flip flops.
0 Kudos
12 Replies
Altera_Forum
Honored Contributor II
1,290 Views

Just an update, looks like this issue is being caused by noise. If I simply unplug a flat cable from my Cyclone IV development board that connects 10 outputs to some buffers/drivers, the problem stops. The I/Os feeding this cable carry ~5MHz signals, including CLK, Latch & 6-bit Data. 

 

Yet, I don't think this should happen. I know FPGAs are not MCUs, but are they that sensitive? Could also be the development board I bought, which's not from Altera. Maybe it's a bad design board.
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

I think your mistake is using the SPI clock as an actual clock in the design. Use a clock from the PLL as the clock to your module (and entire FPGA design if possible). This should be much faster than the SPI clock. Run the SPI clock through 2 registers to synchronize it to the FPGA clock. After that, run it to your SPI slave. Compare the SPI clock state to the previous state to detect rising or falling edges. Only sample the SPI clock on FPGA clock rising edges.  

 

FPGAs are finicky about signals that are not registered to one of their clocks. Search "asynchronous FPGA inputs" for further reading.
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

Thanks. I'll try that and post the results here. Its strange, though, because I've seen SPI examples in VHDL for FPGAs that simply have an "IF rising_edge(SPI_CLK) THEN..." inside, and without sync with any other clock. And I've done this using an old Lattice and Cypress CPLDs in the past. It's just this FPGA that's giving me trouble. 

 

Also, I tried creating a 4th SPI signal called SPI_CHK. This signal will be low at the 1st clock (1st rising and falling SPI_CLK edges), high on the 2nd, low on the 3rd, and so on. Then, once the "IF rising_edge(SPI_CLK) THEN..." reads a bit from SPI_MOSI, it'll only read another if SPI_CHK is the opposite. So if SPI_CLK was triggering rising_edge(SPI_CLK) twice on the same rising edge, or triggering on a falling edge as well, this would stop it. It helped a little, but it's far from solving the issue. I was like... "how come???" 

 

And finally, I did not understood this part of your post: "Run the SPI clock through 2 registers to synchronize it to the FPGA clock". I understood that a "routine" on the main FPGA clock should constantly compare SPI_CLK with the state it was on the previous clock. If it changed, then read SPI_MOSI. This would avoid a slow-rising edge of SPI triggering rising_edge(SPI_CLK) twice. 

 

Thanks for your help!
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

 

--- Quote Start ---  

Its strange, though, because I've seen SPI examples in VHDL for FPGAs that simply have an "IF rising_edge(SPI_CLK) THEN..." inside, and without sync with any other clock. And I've done this using an old Lattice and Cypress CPLDs in the past. It's just this FPGA that's giving me trouble. 

--- Quote End ---  

 

It's surely possible to use SPI SCK as clock, but the rise time must comply with the FPGA requirements. Old CPLD are considerably slower and may also provide ST clock input buffers. Enabling Cyclone IV bus hold for the SCK input might help to speed up the clock edge. 

 

For typical processor SPI speeds, processing SCK in the internal FPGA clock domain as suggested by eng1 is the usual and better way. Using SCK as FPGA clock is preferable for high SPI speeds. Synchronization of the received data to the internal FPGA clock is however required, otherwise you can expect nasty occasional data errors.
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

 

--- Quote Start ---  

 

And finally, I did not understood this part of your post: "Run the SPI clock through 2 registers to synchronize it to the FPGA clock".  

 

--- Quote End ---  

 

 

This document explains what I was getting at: 

https://www.altera.com/en_us/pdfs/literature/wp/wp-01082-quartus-ii-metastability.pdf 

Page 3 figure 3 shows the two registers I was talking about under the "Synchronization Chain" label.  

 

 

--- Quote Start ---  

I've seen SPI examples in VHDL for FPGAs that simply have an "IF rising_edge(SPI_CLK) THEN..." 

--- Quote End ---  

 

You will be better off in the long run to use one clock in the FPGA for everything (unless it is absolutely not possible). Use clock enables to simulate slower clocks when needed. Use the synchronize chain from the document on every signal that comes into the FPGA. Asynchronous inputs and metastability can cause incredibly strange operation.
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

My development board has a 50MHz oscillator at PIN 25 of the Cyclone IV FPGA. I'm simply creating a signal called clk: in std_logic; , using process (clk) and placing if rising_edge(clk) then inside it. Then, under Assignments/Pin Planner, I'm assigning the CLK input to PIN_25 with I/O standard 3.3-V LVTTL. Since I don't want to modify the clock frequency, I assume I don't need to use a PLL. Am I doing this right?  

 

I saw on the PDF posted above about Metastability and that might explain some issues I'm having as I'm simply using the main clock as an input to a register.
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

By the way, I've implemented the SPI using the main FPGA clock: I have the SPI_CS, SPI_CLK and SPI_MOSI input signals from the main CPU that's sending SPI data to the FPGA (no synchronize register chain yet), and I've created the LAST_SPI_CS and LAST_SPI_CLK boolean variables, which contains the state of SPI_CS and SPI_CLK on the previous CLK edge (main FPGA clock). So, when SPI_CLK='1' and LAST_SPI_CLK='0' (while SPI_CS='0'), I'll sample SPI_MOSI and shift it into the array (spi_in<=spi_in(13 downto 0) & spi_mosi;). If SPI_CS goes high, I'll then process the received data in the SPI_IN array. 

 

And wow... it really doesn't work! I kinda receive part of the data, but a lot goes missing or arrives shifted left/right. I then tried to lower the SPI signals sampling rate using simply a variable and a if clk_n=5 then clk_n=0 ... (spi code here) ... else clk_n:=clk_n+1;, and it got a lot better, but still its a mess. 

 

I understand about Metastability and how the synchronize chain of registers will help with that phenomenon, but what I don't understand is that I've placed plenty of NOPs before and after the rising SPI_CLK edge on the main CPU, so that when the FPGA sees a rising SPI_CLK edge it has plenty of setup and hold time to sample SPI_MOSI. So I don't see how Metastability is being the issue here. Besides, what's really happening is that apparently lots of SPI_CLK & SPI_CS edges are being lost/triggering twice, which wasn't happening nearly that much on the original SPI slave (using if rising_edge(spi_clk) then...). 

 

So, could this still be a Metastability issue and the synchronize register chain will help? I'll study some more about the register chain to make sure I understand how to implement it, and then I'll try it.
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

A better way to clarify what you are doing would be to post the actual code.

0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

Well, I've removed the non-SPI related code, otherwise it'd be too big. 

 

ENTITY Video IS PORT (CLK : IN STD_LOGIC; -- Main FPGA clock (50MHz XTAL). It's set on the Pin Planner as simply 3.3-V LVTTL / 8mA. Placed on a clock input pin SPI_CS : IN STD_LOGIC; SPI_CLK : IN STD_LOGIC; SPI_MOSI : IN STD_LOGIC); END Video; ARCHITECTURE Refresh OF Video IS signal SPI_IN : STD_LOGIC_VECTOR (14 DOWNTO 0); -- Received SPI data is shifted here, MSB first signal SPI_CLK_LAST : STD_LOGIC :='0'; signal SPI_CS_LAST : STD_LOGIC :='0'; BEGIN PROCESS (CLK) -- variable CLKCNT : integer :=0; -- This is used to lower the SPI sampling rate vs. main clock BEGIN IF rising_edge(CLK) THEN -- IF CLKCNT=5 then -- This is used to lower the SPI sampling rate vs. main clock. With this uncommented, the SPI slave -- CLKCNT:=0; -- works much better, but it's still losing a lot of data/edges. IF SPI_CS='0' THEN IF SPI_CS_LAST='1' THEN -- (Some debug code would also go here. Thus this IF.) SPI_CS_LAST<='0'; end if; if SPI_CLK='1' and SPI_CLK_LAST='0' then SPI_CLK_LAST<='1'; SPI_IN<=SPI_IN(13 downto 0) & SPI_MOSI; elsif SPI_CLK='0' then SPI_CLK_LAST<='0'; end if; ELSIF SPI_CS='1' and SPI_CS_LAST='0' THEN SPI_CS_LAST<='1'; -- Here I'll process the data received in SPI_IN END IF; -- ELSE -- This is used to lower the SPI sampling rate vs. main clock -- CLKCNT:=CLKCNT+1; -- END IF; END IF; END PROCESS; 

 

Edit: Sorry, I'm kinda of a forum noob, didn't see the <CODE> thing! 

 

Once again, the SPI_MOSI signal is kept stable for a long time before and after the rising SPI_CLK. So Tsu and Th are surely being met. For some reason I think the problem is the way I'm inputting the main 50MHz clock from the xtal.
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

Well, I checked and my CLK is indeed being routed thru GCLK3; a global clock. So that's probably not the problem. 

 

Now, I made a test. I've made the FPGA copy the received SPI_CLK and SPI_MOSI to two other output signals upon a SPI_CLK rising edge detection, and copy the received SPI_CS to another output signal upon an SPI_CS event. 

 

Then, using a logic analyzer, I captured the SPI CS, CLK and MOSI directly from the CPU which are entering the FPGA, and the SPI CS, CLK and MOSI that the FPGA is outputting as the copies of those signals. The result was this: 

 

https://www.alteraforum.com/forum/attachment.php?attachmentid=13964  

 

The FPGA is receiving those perfectly. I checked pages and pages of captured data, and the FPGA is outputting exactly what it's receiving, with the expected shift in the SPI_MOSI copy, which since the FPGA captures it only upon a rising SPI_CLK, will only be copied at that event. So, everything is as expected. 

 

Looking at that, it doesn't look like its a metastability issue. There's plenty of Tsu and Th for SPI_MOSI.  

 

Now, the weird part is: I made a counter of how many rising SPI_CLK edges are being received per SPI_CS low period. This counter increments inside the same IF that copies SPI_MOSI to the output pin and shifts it into the array, and is cleared when SPI_CS goes low (falling edge; start of a new set of SPI data). I've then made the 8 LEDs on my development kit show the value of this counter on every rising edge of SPI_CS (when a new set of SPI data has been fully received). See the lines in bold on the code below: 

 

ENTITY Video IS PORT ( CLK : IN STD_LOGIC; -- Main FPGA clock (50MHz XTAL). It's set on the Pin Planner as simply 3.3-V LVTTL / 8mA. Placed on a clock input pin SPI_CS : IN STD_LOGIC; SPI_CLK : IN STD_LOGIC; SPI_MOSI : IN STD_LOGIC; LEDS : OUT STD_LOGIC_VECTOR(7 downto 0)); -- LEDS on the development board END Video; ARCHITECTURE Refresh OF Video IS signal SPI_IN : STD_LOGIC_VECTOR (14 DOWNTO 0); -- Received SPI data is shifted here, MSB first signal SPI_CLK_LAST : STD_LOGIC :='0'; signal SPI_CS_LAST : STD_LOGIC :='0'; BEGIN PROCESS (CLK) BEGIN IF rising_edge(CLK) THEN IF SPI_CS='0' THEN IF SPI_CS_LAST='1' THEN SPI_IN_CNT<="00000000"; SPI_CS_LAST<='0'; end if; if SPI_CLK='1' and SPI_CLK_LAST='0' then SPI_CLK_LAST<='1'; SPI_IN<=SPI_IN(13 downto 0) & SPI_MOSI; SPI_IN_CNT<=SPI_IN_CNT+1; elsif SPI_CLK='0' then SPI_CLK_LAST<='0'; end if; ELSIF SPI_CS='1' and SPI_CS_LAST='0' THEN SPI_CS_LAST<='1'; -- Here I'll process the data received in SPI_IN LEDS<=SPI_IN_CNT; END IF; END IF; END PROCESS;  

 

By what I'm seeing on my logic analyzer, the LEDs should always show 12, as there are 12 rising clock edges per SPI_CS low period, right? Nope. The LEDs are showing all sorts of values between ~3 and ~67 (guessing the values I've seen on the LEDs - I made a HOLD button to freeze the LEDs at the last final value). 

 

What could it be then? Could it still be metastability?
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

Well, I've made another post above this one, which contains images and hasn't been approved yet, but just to report: 

 

I've studied about synchronization chain, from the document Eng1 posted, as well as other pages about it that I found on Google, and created a 3-stage chain (3 just to be safe) within the same "IF rising_edge(CLK)" of all my code (is this ok?). Here's how it looks: 

 

IF rising_edge(CLK) THEN SPI_CLK_SYNC(0)<=SPI_CLK_SYNC(1); SPI_CLK_SYNC(1)<=SPI_CLK_SYNC(2); SPI_CLK_SYNC(2)<=SPI_CLK; SPI_CS_SYNC(0)<=SPI_CS_SYNC(1); SPI_CS_SYNC(1)<=SPI_CS_SYNC(2); SPI_CS_SYNC(2)<=SPI_CS; (...)  

 

Since I want to detect edges of both the SPI_CLK and SPI_CS, I thought I'd have to synchronize both signals. Well... it worked perfectly! 

I'm very amazed about these FPGA phenomenons, and it'd take a long time for me to find out that this was the problem I was having if it weren't for you guys. Thank you both very much!
0 Kudos
Altera_Forum
Honored Contributor II
1,290 Views

It is really amazing how bad things can go if the inputs are not sync'd up. 

 

I have found two stages to be adequate. You could use the third as your previous state. 

 

IF rising_edge(CLK) THEN SPI_CLK_LAST<=SPI_CLK_SYNC(1); SPI_CLK_SYNC(1)<=SPI_CLK_SYNC(2); SPI_CLK_SYNC(2)<=SPI_CLK;  

 

Also, synchronize every signal coming into the FPGA (SPI_MOSI, too) unless there is some good reason not to do so.
0 Kudos
Reply