Search code examples
vhdlfpgasupernintendo

VHDL - SNES Interface via controller port using FPGA


I am working on trying to interface a cheap FPGA (the ep2c5t144 Altera Cyclone II mini board) with a SNES in order to ACT as a SNES controller. So far, it seems to work on and off... with the current problem being, it works for about 1 second after switched on... but then seems to get stuck in a state until it is reset.

Since I have spent a long time looking at the code for a logic issue, I'm starting to wonder whether it's some strange quirk of using FPGAs, but I've already tried testing for any states which aren't defined, and that hasn't fixed the problem. I will post the SNES code below, and the output from my cheap logic analyser which shows the problem. Warning, the code is quite messy... especially with me changing thing around to try to fix it. Any ideas at all with be much appreciated!

Many thanks in advance for any help!

Problem from logic analyser:

When a request works - State transitions occur as expected

When a request fails - SEEMS to incorrectly transition directly to "working" state and get stuck for some reason

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;


entity snes_controller is
    generic (
        hp : integer := 300
    );
    port    (
        clk       : in std_logic;
        latch    : in std_logic;
        data      : out std_logic := '0';
        clock     : in std_logic;
        enable    : in std_logic;
        btn_B : in std_logic;
        btn_Y : in std_logic;
        btn_select : in std_logic;
        btn_start : in std_logic;
        btn_up : in std_logic;
        btn_down : in std_logic;
        btn_left : in std_logic;
        btn_right : in std_logic;
        btn_A : in std_logic;
        btn_X : in std_logic;
        btn_L : in std_logic;
        btn_R : in std_logic;
        helpA : out std_logic := '0';
        helpB : out std_logic := '0';
        helpC : out std_logic := '0';
        helpD : out std_logic := '0';
        helpE : out std_logic := '0'
    );
end entity;

architecture Behav of snes_controller is

    signal buttons : unsigned(16 downto 0) := "10000000000000000";

    type state_type is (s_idle, s_latching_1, s_latching_2, s_working);
    signal state : state_type := s_idle;

    type cycle_type is (c_high, c_low);
    signal cycle : cycle_type := c_high;

begin       

    process (clk)
        variable i : integer range 0 to 16;
        variable count : integer range 0 to hp;
    begin   
        if(rising_edge(clk)) then

            data <= not buttons(i);

            if(state = s_latching_1 or state = s_latching_2 or state = s_working) then
                if(count < hp) then
                    count := count+1;
                else
                    count := 0;

                    if(state = s_latching_1) then
                        if(latch = '1') then
                            state <= s_latching_2;
                            buttons(0) <= btn_B;
                            buttons(1) <= btn_Y;
                            buttons(2) <= btn_select;
                            buttons(3) <= btn_start;
                            buttons(4) <= btn_up;
                            buttons(5) <= btn_down;
                            buttons(6) <= btn_left;
                            buttons(7) <= btn_right;    
                            buttons(8) <= btn_A;
                            buttons(9) <= btn_X;
                            buttons(10) <= btn_L;
                            buttons(11) <= btn_R;
                        else
                            state <= s_idle;
                        end if;
                    elsif(state = s_latching_2) then
                        state <= s_working;
                        i := 0;
                        cycle <= c_high;
                    elsif(state = s_working) then       
                        if(latch = '1') then
                            state <= s_idle;
                            helpD <= '1';
                        elsif(cycle = c_low) then
                            cycle <= c_high;
                            if(i < 16) then
                                i := i+1;
                            else
                                state <= s_idle;
                                helpD <= '0';
                                helpE <= '0';
                            end if;
                        else
                            cycle <= c_low;
                        end if;
                    end if;

                end if;
            elsif(state = s_idle) then
                if(latch = '1') then
                    state <= s_latching_1;
                    count := 0;
                    i := 0;
                end if;
            else
                helpE <= '1';
                state <= s_idle;
                count := 0;
                i := 0;
            end if;

        end if;

    end process;

    process(state)
    begin
        if(state = s_idle) then
            helpA <= '0';
            helpB <= '0';
        elsif(state = s_latching_1) then
            helpA <= '1';
            helpB <= '0';
        elsif(state = s_latching_2) then
            helpA <= '0';
            helpB <= '1';
        elsif(state = s_working) then
            helpA <= '1';
            helpB <= '1';
        else
            helpA <= clk;
            helpB <= not clk;
        end if;

        if(cycle = c_low) then
            helpC <= '0';
        elsif(cycle = c_high) then
            helpC <= '1';
        end if;
    end process;

end Behav;

Solution

  • You are using asynchronous external inputs and feed them into a synchronous, clock-based, state machine. Metastability in sampling may lead to your problem. Make sure you implement at least a two-flop synchronizer for every input signal.

    Read more about it here: http://webee.technion.ac.il/~ran/papers/Metastability%20and%20Synchronizers.posted.pdf

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    
    entity snes_controller is
        generic (
            hp : integer := 300
        );
        port    (
            clk       : in std_logic;
            latch    : in std_logic;
            data      : out std_logic := '0';
            clock     : in std_logic;
            enable    : in std_logic;
            btn_B : in std_logic;
            btn_Y : in std_logic;
            btn_select : in std_logic;
            btn_start : in std_logic;
            btn_up : in std_logic;
            btn_down : in std_logic;
            btn_left : in std_logic;
            btn_right : in std_logic;
            btn_A : in std_logic;
            btn_X : in std_logic;
            btn_L : in std_logic;
            btn_R : in std_logic;
            helpA : out std_logic := '0';
            helpB : out std_logic := '0';
            helpC : out std_logic := '0';
            helpD : out std_logic := '0';
            helpE : out std_logic := '0'
        );
    end entity;
    
    architecture Behav of snes_controller is
    
        signal synch0 : unsigned(11 downto 0) := (others => '0');
        signal synch1 : unsigned(11 downto 0) := (others => '0');
        signal synch2 : unsigned(11 downto 0) := (others => '0');
        signal buttons : unsigned(16 downto 0) := "10000000000000000";
    
        type state_type is (s_idle, s_latching_1, s_latching_2, s_working);
        signal state : state_type := s_idle;
    
        type cycle_type is (c_high, c_low);
        signal cycle : cycle_type := c_high;
    
    begin       
    
        process (clk)
            variable i : integer range 0 to 16;
            variable count : integer range 0 to hp;
        begin   
            if(rising_edge(clk)) then
                                synch0(0) <= btn_B;
                                synch0(1) <= btn_Y;
                                synch0(2) <= btn_select;
                                synch0(3) <= btn_start;
                                synch0(4) <= btn_up;
                                synch0(5) <= btn_down;
                                synch0(6) <= btn_left;
                                synch0(7) <= btn_right;    
                                synch0(8) <= btn_A;
                                synch0(9) <= btn_X;
                                synch0(10) <= btn_L;
                                synch0(11) <= btn_R;
                synch1 <= synch0;
                synch2 <= synch1;    
    
                data <= not buttons(i);
    
                if(state = s_latching_1 or state = s_latching_2 or state = s_working) then
                    if(count < hp) then
                        count := count+1;
                    else
                        count := 0;
    
                        if(state = s_latching_1) then
                            if(latch = '1') then
                                state <= s_latching_2;
                                buttons(11 downto 0) <= synch2(11 downto 0);
                            else
                                state <= s_idle;
                            end if;
                        elsif(state = s_latching_2) then
                            state <= s_working;
                            i := 0;
                            cycle <= c_high;
                        elsif(state = s_working) then       
                            if(latch = '1') then
                                state <= s_idle;
                                helpD <= '1';
                            elsif(cycle = c_low) then
                                cycle <= c_high;
                                if(i < 16) then
                                    i := i+1;
                                else
                                    state <= s_idle;
                                    helpD <= '0';
                                    helpE <= '0';
                                end if;
                            else
                                cycle <= c_low;
                            end if;
                        end if;
    
                    end if;
                elsif(state = s_idle) then
                    if(latch = '1') then
                        state <= s_latching_1;
                        count := 0;
                        i := 0;
                    end if;
                else
                    helpE <= '1';
                    state <= s_idle;
                    count := 0;
                    i := 0;
                end if;
    
            end if;
    
        end process;
    
        process(state)
        begin
            if(state = s_idle) then
                helpA <= '0';
                helpB <= '0';
            elsif(state = s_latching_1) then
                helpA <= '1';
                helpB <= '0';
            elsif(state = s_latching_2) then
                helpA <= '0';
                helpB <= '1';
            elsif(state = s_working) then
                helpA <= '1';
                helpB <= '1';
            else
                helpA <= clk;
                helpB <= not clk;
            end if;
    
            if(cycle = c_low) then
                helpC <= '0';
            elsif(cycle = c_high) then
                helpC <= '1';
            end if;
        end process;
    
    end Behav;
    

    Additionally, I suggest to create some sort of filter to handle debouncing of button clicks. http://www.eng.utah.edu/~cs5780/debouncing.pdf