Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[SOLVED] Unstable state machine

Status
Not open for further replies.

snipex

Junior Member level 3
Joined
May 23, 2016
Messages
29
Helped
2
Reputation
4
Reaction score
2
Trophy points
3
Activity points
284
So I'm working on an SPI module on FPGA. To control this SPI module I wrote a state machine.
The state machine resets itself after it falls into unknown state. This is a wanted behaviour but it happens too often.
I tried everything to fix this but nothing works.

Any ideas would be helpfull.

Code:

SPI_u:
Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;

entity SPI_u is
	port(	sclk: in std_logic;
			mosi: in std_logic;
			miso: out std_logic;
			spi_ss: in std_logic;
			RX_Data: out std_logic_vector(7 downto 0);
			busy: out std_logic;
			trdy: out std_logic;
			rrdy: out std_logic;
			roe: out std_logic;
			reset_n: in std_logic;
			TX_Data: in std_logic_vector(7 downto 0);
			TX_load_en: in std_logic;
			RX_req: in std_logic;
			CLK: in std_logic
		);
end SPI_u;

architecture rtl of SPI_u is

signal mosi_reg: std_logic_vector(7 downto 0):=x"FF";
signal miso_reg: std_logic_vector(7 downto 0):=x"FF";

signal mosi_s: std_logic;
signal miso_s: std_logic;
signal sclk_s: std_logic;
signal sclk_2s: std_logic;
signal spi_ss_s: std_logic;

signal bit_counter: unsigned(3 downto 0):= "0000";
signal rrdy_s: std_logic;
signal trdy_s: std_logic;

signal sclk_state: std_logic:= '0';

signal first_flop: std_logic;
signal second_flop: std_logic;
signal third_flop: std_logic;
signal rising_edge_sclk: std_logic;
signal falling_edge_sclk: std_logic;

begin

mosi_s <= mosi;
miso <= miso_s;
spi_ss_s <= spi_ss;

ris_edge_det: process(CLK)
begin
	if rising_edge(CLK) then
		first_flop <= sclk;
		second_flop <= first_flop;
		third_flop <= second_flop;
	end if;
end process;

shift_reg: process(CLK)
begin

if rising_edge(CLK) then	

	if spi_ss_s = '0' then
		if first_flop = '1' AND second_flop = '0' then
			mosi_reg(7 downto 1) <= mosi_reg(6 downto 0);		--Shift register for MOSI
			mosi_reg(0) <= mosi_s;								--MOSI register input
			bit_counter <= bit_counter + 1;
			miso_reg(7 downto 0) <= miso_reg(6 downto 0) & '0';
		elsif first_flop = '0' AND second_flop = '1' then				--sclk_s
			--miso_reg(7 downto 0) <= miso_reg(6 downto 0) & '0';	
		end if;
	end if;
	
	if bit_counter = "1000" OR reset_n = '0' OR spi_ss_s = '1' then	--Clear bit counter
		bit_counter <= "0000";
	end if;

	if rrdy_s = '1' AND RX_req = '1' then		--Latch data to RX_data when requested
		RX_Data <= mosi_reg;
	elsif reset_n = '0' then					--Reset condition
		RX_Data <= x"0F";
		mosi_reg <= x"00";
	end if;
	
	if TX_load_en = '1' AND trdy_s = '1' then	--Load data to miso_reg when register is available and TX_load_en
		miso_reg <= TX_Data;
	elsif reset_n = '0' then					--Reset condition
		miso_reg <= x"00";					-----------------####################------------------
	end if;
	
	if RX_req = '1' OR reset_n = '0' then		--Register overflow status
		roe <= '0';
	elsif rrdy_s = '1' AND bit_counter = "1000" then
		roe <= '1';
	end if;
	
	if TX_load_en = '1' then					--Status register for MISO reg
		trdy_s <= '0';
	elsif bit_counter = "1000" OR reset_n = '0' then
		trdy_s <= '1';
	end if;
	
	if RX_req = '1' OR reset_n = '0' then		--Status register for MOSI reg
		rrdy_s <= '0';
	elsif bit_counter = "1000" then
		rrdy_s <= '1';
	end if;
end if;
end process;

busy <= NOT spi_ss;
rrdy <= rrdy_s;
trdy <= trdy_s;

miso_s <= miso_reg(7);		--MSB bit of MISO reg always ready on MISO line

end rtl;



SPI_u_ctrl(state machine) :

Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;

entity SPI_u_ctrl is
	port(	RX_Data: in std_logic_vector(7 downto 0);
			busy: in std_logic;
			trdy: in std_logic;
			rrdy: in std_logic;
			roe: in std_logic;
			RST: in std_logic;
			TX_Data: out std_logic_vector(7 downto 0);
			TX_load_en: out std_logic;
			RX_req: out std_logic;
			PWM: out std_logic_vector(7 downto 0);
			Command: out std_logic_vector(7 downto 0);
			clk: in std_logic;
			reset: out std_logic;
			rrdy_o: out std_logic;
			trdy_o: out std_logic;
			state_o: out std_logic_vector(3 downto 0)
		);
end SPI_u_ctrl;

architecture rtl of SPI_u_ctrl is

signal pwm_s: std_logic_vector(7 downto 0):= x"00";
signal Command_s: std_logic_vector(7 downto 0):= x"00";


type state_value is (Reset_SPI, Wait_s, Wait_RRDY, PWM_latch, Wait_2, Wait_RRDY2, Command_latch, Wait_comm, Wait_dummy, Wait_clk1, Wait_clk2);
signal state, next_state: state_value;

attribute syn_encoding: string;
attribute syn_encoding of State: signal is "onehot,safe";


signal wait_clk: std_logic:= '0';

begin

next_state_p: process(clk, rst)
begin
	if rst = '0' then
		state <= Reset_SPI;
	elsif rising_edge(clk) then
		state <= next_state;
	end if;	
end process;

state_machine: process(state, rx_data, busy, rrdy, trdy)
begin
	next_state <= Reset_SPI;
	RX_req <= '0';
	TX_load_en <= '0';

	case state is
		when Reset_SPI =>
			state_o <= "0000";
			if busy = '0' then
				RX_req <= '1';					--Dummy read to clear rrdy
				TX_Data <= x"FF";				--First byte to send
				TX_load_en <= '1';				--TX load enable
				next_state <= Wait_s;
				wait_clk <= '0';				
			else
				TX_load_en <= '0';
				RX_req <= '0';
				reset <= '1';
			end if;
		when Wait_s =>
			state_o <= "0001";
			TX_load_en <= '0';				--TX load disable
			RX_req <= '0';					--RX_req disable
			if busy = '1' then
				next_state <= Wait_RRDY;
			else
				next_state <= Wait_s;
			end if;
		when Wait_RRDY =>
			state_o <= "0010";
			if rrdy = '1' then
				RX_req <= '1';
				next_state <= Wait_clk1;
			else
				next_state <= Wait_RRDY;
			end if;
		when Wait_clk1 =>
			state_o <= "0011";
			next_state <= PWM_latch;
		when PWM_latch =>
			state_o <= "0100";
			pwm_s <= RX_Data;
			RX_req <= '0';
			if trdy = '1' then
				TX_Data <= RX_Data;
				TX_load_en <= '1';
				next_state <= Wait_2;
			else 
				next_state <= PWM_latch;
			end if;
		when Wait_2 =>
			state_o <= "0101";
			next_state <= Wait_RRDY2;
		when Wait_RRDY2 =>
			state_o <= "0110";
			TX_load_en <= '0';
			if rrdy = '1' then
				RX_req <= '1';
				next_state <= Wait_clk2;
			else
				next_state <= Wait_RRDY2;
			end if;
		when Wait_clk2 =>
			state_o <= "0111";
			next_state <= Command_latch;
		when Command_latch =>
			state_o <= "1000";
			command_s <= RX_Data;
			RX_req <= '0';
			if trdy = '1' then
				TX_Data <= RX_Data;
				next_state <= Wait_comm;
			else
				next_state <= Command_latch;
			end if;
		when Wait_comm =>
			state_o <= "1001";
			TX_load_en <= '1';
			next_state <= Reset_SPI;
		when others =>
			next_state <= Reset_SPI;
	end case;
end process;

PWM <= pwm_s;
Command <= command_s;

end rtl;
 

Are your input signals (rx_data, busy, rrdy, trdy) syncrhonised to the clk? if not, then you will have lots of timing issues, probably causing the unknown states. With these synchronised to clk it should never fall into an unknwon state.

Also, a lot of your outputs produce latches - again these can cause issues. The latches are created because you do not assign all outputs in all states.

I recommend synchronising everything (even the outputs) to the clk. The easiest way to do with would be to have a single process state machine, rather than the 2 process one you have now.
 
  • Like
Reactions: snipex

    snipex

    Points: 2
    Helpful Answer Positive Rating
Provided this is a SPI slave, I would only expect the external inputs (SCK, MOSI, SS) to be originated from a foreign clock domain and the design internal signals to be synchronous.

At least SS which is sampled by multiple registers in the design would also need synchronization, SCK has already a double FF synchronizer, MOSI can possibly work without it. But applying the same clock cycle delay to MOSI would increase the useable maximum SCK speed.
 
  • Like
Reactions: snipex

    snipex

    Points: 2
    Helpful Answer Positive Rating
Thank you both. I tried synchronizing the SS signal and it worked. I can't belive I wasted 3 days on this.
As you suggested I am also going to sync all other input signals.

TrickyDicky could you explain why latches cause trouble. I made them on purpose so the input data is always available to the top module.
 

Latches both produce a long combinational path (transparent and feedback) that may not meet timing and they are very difficult to apply timing constraints to to ensure they always meet timing.

Using D-Flip Flops is easy to do in the tool chain of all FPGAs the timing constraints are easy and just work out of the box. Adding latches requires knowing exactly how to constrain a design at the path level to specific internal design nodes a somewhat more advanced methodology than just add a clock constraint and push run.

- - - Updated - - -

edit,
Of course if you are using a very old part like a virtex 2 they actually have cells that can be implemented as latches or FFs and they do get timed correctly if the latch signal is a global clock. But the latest parts don't have this feature and the latch is implemented in LUTs, so routing becomes a critical path.
 
  • Like
Reactions: snipex

    snipex

    Points: 2
    Helpful Answer Positive Rating
I checked my FPGA datasheet and it seems like it has latches. Does this mean that I can use them?

I'm using Lattice MachXO FPGA.

machxo_latch.PNG
 

You can, but latches typically indicate a design oversight or a poor design. It is probably better to fix the issues with the latches.
 
  • Like
Reactions: snipex

    snipex

    Points: 2
    Helpful Answer Positive Rating
Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top