Synchronized SPI communication between three FPGAs using VHDL - Page 1
Forum section: electronics / general

author: eds
author_rank: Newbie
timestamp: February 23, 2024, 07:54:53 am
content: Hello everyone,I'm relatively new to working with FPGAs, and while I've managed projects without real-time communication needs, I'm now faced with a task that requires communication between three FPGAs. One of these FPGAs will act as the master, controlling the clock and other essential functions of the system. The other two FPGAs will essentially duplicate tasks simultaneously, allowing one to take over if the other fails.Each FPGA has three connection lines available for communication. In my setup, I've designated one line for the clock, one for SDO (Serial Data Out), and the remaining one for SDI (Serial Data In), thus enabling full-duplex communication. To check whether I receive messages from both FPGAs, I've implemented a test pin that I monitor using an oscilloscope. However, I've noticed that there's a half-cycle phase difference between the test pins of the two FPGAs.I'm seeking guidance on resolving this issue and ensuring proper synchronization between the FPGAs for reliable communication. Any help or suggestions would be greatly appreciated.This is my controller code for both of the slave FPGAsCode:library IEEE;use IEEE.STD_LOGIC_1164.ALL;entity FPGAKONE_CONTROLLER isPort ( reset: in STD_LOGIC;CLK : in STD_LOGIC; -- Clock lineSDI : in STD_LOGIC; -- Serial Data InSDO : out STD_LOGIC; -- Serial Data OutSCK : out STD_LOGIC; -- Clock outputshift_reg_in : out STD_LOGIC_VECTOR(7 DOWNTO 0));end FPGAKONE_CONTROLLER;architecture Behavioral of FPGAKONE_CONTROLLER issignal shift_reg_out : std_logic_vector(7 downto 0):= "10101010";--signal shift_reg_in : std_logic_vector(7 downto 0); -- Input shift registersignal bit_counter : integer range 0 to 15 := 0; -- Bit countertype state_type is (IDLE, TRANSFER);signal state : state_type := IDLE;begin-- Process for FSMprocess(CLK)beginif CLK'event and CLK = '1' thencase state iswhen IDLE =>if reset = '0' thenstate <= TRANSFER;elseshift_reg_in <= "00000000";state <= IDLE;end if;when TRANSFER =>if bit_counter = 0 thenSDO <= shift_reg_out(7);SCK <= '1';shift_reg_in(7) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 1 thenSCK <= '0';bit_counter <= bit_counter + 1;elsif bit_counter = 2 thenSDO <= shift_reg_out(6);SCK <= '1';shift_reg_in(6) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 3 thenSCK <= '0';bit_counter <= bit_counter + 1;elsif bit_counter = 4 thenSDO <= shift_reg_out(5);SCK <= '1';shift_reg_in(5) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 5 thenSCK <= '0';bit_counter <= bit_counter + 1;elsif bit_counter = 6 thenSDO <= shift_reg_out(4);SCK <= '1';shift_reg_in(4) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 7 thenSCK <= '0';bit_counter <= bit_counter + 1;elsif bit_counter = 8 thenSDO <= shift_reg_out(3);SCK <= '1';shift_reg_in(3) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 9 thenSCK <= '0';bit_counter <= bit_counter + 1;elsif bit_counter = 10 thenSDO <= shift_reg_out(2);SCK <= '1';shift_reg_in(2) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 11 thenSCK <= '0';bit_counter <= bit_counter + 1;elsif bit_counter = 12 thenSDO <= shift_reg_out(1);SCK <= '1';shift_reg_in(1) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 13 thenSCK <= '0';bit_counter <= bit_counter + 1;elsif bit_counter = 14 thenSDO <= shift_reg_out(0);SCK <= '1';shift_reg_in(0) <= SDI;bit_counter <= bit_counter + 1;elsif bit_counter = 15 thenSCK <= '0';bit_counter <= 0;state <= IDLE;elseSDO <= '0';bit_counter <= 0;state <= IDLE;end if;end case;end if;end process;end Behavioral;and this is my code for the slaves, which is also the same for both of the FPGAs:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;entity FPGAH_SLAVE isPort ( reset: in STD_LOGIC;OSC_K : in STD_LOGIC;SDI : in STD_LOGIC; -- Serial Data InSDO : out STD_LOGIC; -- Serial Data OutSCK : in STD_LOGIC; -- Clock outputshift_reg_in : out std_logic_vector(7 downto 0));end FPGAH_SLAVE;architecture Behavioral of FPGAH_SLAVE istype state_type is (IDLE, TRANSFER);signal state : state_type := IDLE;signal shift_reg_out : std_logic_vector(7 downto 0):="01010101"; -- Output shift registersignal bit_counter : integer range 0 to 7 := 0; -- Bit counterbegin-- Process for FSMprocess(OSC_K)beginif rising_edge(OSC_K) thencase state iswhen IDLE =>if reset = '0' thenstate <= TRANSFER;elseshift_reg_in <= "00000000";state <= IDLE;end if;when TRANSFER =>if SCK = '1' thenif bit_counter = 0 thenSDO <= shift_reg_out(7);shift_reg_in(7) <= SDI;elsif bit_counter = 1 thenSDO <= shift_reg_out(6);shift_reg_in(6) <= SDI;elsif bit_counter = 2 thenSDO <= shift_reg_out(5);shift_reg_in(5) <= SDI;elsif bit_counter = 3 thenSDO <= shift_reg_out(4);shift_reg_in(4) <= SDI;elsif bit_counter = 4 thenSDO <= shift_reg_out(3);shift_reg_in(3) <= SDI;elsif bit_counter = 5 thenSDO <= shift_reg_out(2);shift_reg_in(2) <= SDI;elsif bit_counter = 6 thenSDO <= shift_reg_out(1);shift_reg_in(1) <= SDI;elsif bit_counter = 7 thenSDO <= shift_reg_out(0);shift_reg_in(0) <= SDI;elseSDO <= '0';state <= IDLE;bit_counter <= 0;end if;elsebit_counter <= bit_counter + 1;end if;end case;end if;end process;end Behavioral;

author: Daixiwen
author_rank: Frequent Contributor
timestamp: February 23, 2024, 09:15:06 am
content: Are all FPGAs sharing the same clock (CLK, not SCK)?Did you specify timing constraints for the SDI, SDO and SCK signals? You need to specify setup and hold constraints relative to the CLK clock, and if the frequency or the distance between the FPGAs i high, taking into account the propagation time on the PCB.

author: pcprogrammer
author_rank: Super Contributor
timestamp: February 23, 2024, 09:24:46 am
content: On a side note, how did you tie the SDO lines together or has the master separate inputs for each SDO line.I don't see a setup in your outside world connection to make up an open drain output, and when having the signals tied together it might result in nasty collisions.

author: eds
author_rank: Newbie
timestamp: February 23, 2024, 10:14:57 am
content: No, all of the FPGAs have their own oscilllator clock but I use the same oscillator for all three which generates 12 MHz clock. I did not specify any timing constraints for the SDI, SDO and SCK signals. I don't know if the distance is considered high as one of the FPGAs signals are 1-2 cm away from the master and the other around 3-4 cms. I also did not tie the SDO lines together.

author: ejeffrey
author_rank: Super Contributor
timestamp: February 23, 2024, 05:30:34 pm
content: It looks like SCK is only CLK/2. This is not enough to get reliable transfer in asynchronous transfers like this. Because data needs to be either shifted in or out on every edge of CLK, of there is any rate difference or phase drift you will eventually have to drop bits.In principle since your clocks are derived from the same oscillator you can match timing, but I really don't recommend that. In fact if you do that, the SCK signal is essentially superfluous.There are basically two ways to do this. One is to directly clock the slave logic from SCK. This is common in ASICs, since it ends up doing a simple shift register but is not how it is normally done in an FPGA. In an FPGA you normally would treat e a larger divider and keep SCK at or below CLK/4 or CLK/8.

author: davep238
author_rank: Contributor
timestamp: February 27, 2024, 10:55:28 pm
content: Your assignments to SDO and SCK will create latches, since they are not assigned in every "then" selection. They are not assigned in "if reset..". Assign them a value prior to the "case" statement. The state should be reset to IDLE before the case statement. BTW, using "rising_edge(clk)", instead of "clk'event.." has been the preferred way of doing things for several years, although textbooks may still show "clk'event..".if rising_edge(clk) thenif reset = '1' thenstate <= IDLE;SDO <= '0'; -- whatever is appropriateSCK <= '0'; -- etcelse-- default values:SDO <= '0'; -- whatever is appropriateSCK <= '0'; -- etccasewhen...:end case;end if;end if;

author: glenenglish
author_rank: Frequent Contributor
timestamp: February 28, 2024, 07:52:00 am
content: don't forget, the SPI MODE matters alot to how hard the slave has to work the critical path to put data on the bus.consider after issuing a slave register access command, say 8 bits slave address then 8 bits by the master , then the next 8 clocks on the bus will be the register output from the slave.there is a critical path from slave address decoder and register address decode to presenting the first data bit - - you will have half a clock cycle to present the data and this is usually the limit to bus speed- lots of sequential logic in action.also, as a slave, important to de-bounce the SCLK input , use a shift register with say 3 bits and majority vote on the transistion 011, 100 etc as if you get 101, then you have a clock bounce. This applies to Asynchronous SPI interfaces which is what most of them are. I run my async SPI blocks FPGA fabric at 4x to 8x max serial bus speed so there is plenty of time to do good debounceIf the clocks with all the FPGAs that are talking SPI together are synchronous, you can buy a few favors from the hardware config.WRT constraints. if it is an Asynchronous interface, you dont really need to constrain anything except specify the tool not to care about IO pin timings relative to internal clockIf synchronous, you MUST constrain IO delays to clock.

author: Daixiwen
author_rank: Frequent Contributor
timestamp: March 04, 2024, 01:04:02 pm
content: Quote from: davep238 on February 27, 2024, 10:55:28 pmThey won't in this case because it is a clocked process. Not assigning them will just make them keep their current value during the next clock cycle. Latches are created if signals are not assigned in every case of a combinational process.That said I agree that assigning default values before the state machine is a good practice, it makes the code easier to read because you don't have to wonder what the state of the outputs were on the previous cycle. So if you don't explicitly need to keep a signal level from one clock cycle to the other, use default assignments.As Dave said it also good practice need to assign values to your signals at reset, so that you begin in a defined state.