LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;

-- Entity: digilent_jstk2
-- Purpose: Interface controller for the Digilent JSTK2 joystick module via SPI
--          Sends LED color commands and receives joystick/button data
ENTITY digilent_jstk2 IS
    GENERIC (
        DELAY_US : INTEGER := 25;           -- Delay (in microseconds) between two SPI packets
        CLKFREQ : INTEGER := 100_000_000;   -- Frequency of the aclk signal (in Hz)
        SPI_SCLKFREQ : INTEGER := 5_000     -- Frequency of the SPI SCLK clock signal (in Hz)
    );
    PORT (
        aclk : IN STD_LOGIC;        -- Main clock input
        aresetn : IN STD_LOGIC;     -- Active-low asynchronous reset

        -- AXI4-Stream Master Interface: Data going TO the SPI IP-Core (and so, to the JSTK2 module)
        m_axis_tvalid : OUT STD_LOGIC;                      -- Output data valid signal
        m_axis_tdata : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);    -- 8-bit data to send via SPI
        m_axis_tready : IN STD_LOGIC;                       -- SPI IP-Core ready to accept data

        -- AXI4-Stream Slave Interface: Data coming FROM the SPI IP-Core (and so, from the JSTK2 module)
        -- Note: There is no tready signal, so you must be always ready to accept incoming data, or it will be lost!
        s_axis_tvalid : IN STD_LOGIC;                       -- Input data valid signal
        s_axis_tdata : IN STD_LOGIC_VECTOR(7 DOWNTO 0);     -- 8-bit data received via SPI

        -- Joystick and button values read from the JSTK2 module
        jstk_x : OUT STD_LOGIC_VECTOR(9 DOWNTO 0);          -- X-axis joystick position (10-bit, 0-1023)
        jstk_y : OUT STD_LOGIC_VECTOR(9 DOWNTO 0);          -- Y-axis joystick position (10-bit, 0-1023)
        btn_jstk : OUT STD_LOGIC;                           -- Joystick button state (1=pressed)
        btn_trigger : OUT STD_LOGIC;                        -- Trigger button state (1=pressed)

        -- LED color values to send to the JSTK2 module
        led_r : IN STD_LOGIC_VECTOR(7 DOWNTO 0);           -- Red LED intensity (0-255)
        led_g : IN STD_LOGIC_VECTOR(7 DOWNTO 0);           -- Green LED intensity (0-255)
        led_b : IN STD_LOGIC_VECTOR(7 DOWNTO 0)            -- Blue LED intensity (0-255)
    );
END digilent_jstk2;

ARCHITECTURE Behavioral OF digilent_jstk2 IS

    -- Command code for the SetLEDRGB command, see the JSTK2 datasheet
    CONSTANT CMDSETLEDRGB : STD_LOGIC_VECTOR(7 DOWNTO 0) := x"84";

    -- Calculate delay in clock cycles: (delay_period + 1_SPI_clock_period) * clock_frequency
    -- This ensures proper timing between SPI packets as required by JSTK2 datasheet
    CONSTANT DELAY_CLK_CYCLES : INTEGER := (DELAY_US + 1_000_000 / SPI_SCLKFREQ) * (CLKFREQ / 1_000_000) - 1;

    -- State machine type definitions
    TYPE tx_state_type IS (DELAY, SEND_CMD, SEND_RED, SEND_GREEN, SEND_BLUE, SEND_DUMMY);
    TYPE rx_state_type IS (JSTK_X_LOW, JSTK_X_HIGH, JSTK_Y_LOW, JSTK_Y_HIGH, BUTTONS);

    -- State machine signals
    SIGNAL tx_state : tx_state_type := DELAY;           -- Transmit state machine current state
    SIGNAL rx_state : rx_state_type := JSTK_X_LOW;      -- Receive state machine current state

    -- Timing and data storage signals
    SIGNAL tx_delay_counter : INTEGER RANGE 0 TO DELAY_CLK_CYCLES := 0;  -- Counter for inter-packet delay timing
    SIGNAL rx_cache : STD_LOGIC_VECTOR(s_axis_tdata'range);                      -- Temporary storage for multi-byte data reception

BEGIN

    -- Output valid signal control: Set to '1' when we want to send data to SPI IP-Core
    -- Only inactive during DELAY state when waiting between packets
    WITH tx_state SELECT m_axis_tvalid <=
        '0' WHEN DELAY,         -- No transmission during delay
        '1' WHEN SEND_CMD,      -- Send command byte
        '1' WHEN SEND_RED,      -- Send red LED value
        '1' WHEN SEND_GREEN,    -- Send green LED value
        '1' WHEN SEND_BLUE,     -- Send blue LED value
        '1' WHEN SEND_DUMMY;    -- Send dummy byte to complete transaction

    -- Output data multiplexer: Select what data to send based on current TX state
    WITH tx_state SELECT m_axis_tdata <=
        (OTHERS => '0') WHEN DELAY,         -- No data during delay
        CMDSETLEDRGB WHEN SEND_CMD,         -- SetLEDRGB command (0x84)
        led_r WHEN SEND_RED,                -- Red LED intensity value
        led_g WHEN SEND_GREEN,              -- Green LED intensity value
        led_b WHEN SEND_BLUE,               -- Blue LED intensity value
        "01101001" WHEN SEND_DUMMY;         -- Dummy byte to complete 5-byte transaction

    -- TX State Machine: Sends LED color commands to JSTK2 module
    -- Protocol: Command(1) + Red(1) + Green(1) + Blue(1) + Dummy(1) = 5 bytes total > Delay before next command
    -- The delay is required by the JSTK datasheet to ensure proper timing between SPI transactions
    TX : PROCESS (aclk)
    BEGIN
        IF rising_edge(aclk) THEN
            IF aresetn = '0' THEN
                -- Reset: Start in delay state with counter cleared
                tx_state <= DELAY;
                tx_delay_counter <= 0;

            ELSE

                CASE tx_state IS

                    WHEN DELAY =>
                        -- Wait for required delay period between SPI transactions
                        IF tx_delay_counter = DELAY_CLK_CYCLES THEN
                            tx_delay_counter <= 0;     -- Reset counter
                            tx_state <= SEND_CMD;      -- Start new transmission
                        ELSE
                            tx_delay_counter <= tx_delay_counter + 1;  -- Continue counting
                        END IF;

                    WHEN SEND_CMD =>
                        -- Send SetLEDRGB command byte
                        IF m_axis_tready = '1' THEN
                            tx_state <= SEND_RED;      -- Move to red LED transmission
                        END IF;

                    WHEN SEND_RED =>
                        -- Send red LED intensity value
                        IF m_axis_tready = '1' THEN
                            tx_state <= SEND_GREEN;    -- Move to green LED transmission
                        END IF;

                    WHEN SEND_GREEN =>
                        -- Send green LED intensity value
                        IF m_axis_tready = '1' THEN
                            tx_state <= SEND_BLUE;     -- Move to blue LED transmission
                        END IF;

                    WHEN SEND_BLUE =>
                        -- Send blue LED intensity value
                        IF m_axis_tready = '1' THEN
                            tx_state <= SEND_DUMMY;    -- Move to dummy byte transmission
                        END IF;

                    WHEN SEND_DUMMY =>
                        -- Send dummy byte to complete 5-byte transaction
                        IF m_axis_tready = '1' THEN
                            tx_state <= DELAY;         -- Return to delay state
                        END IF;

                END CASE;
            END IF;
        END IF;
    END PROCESS TX;

    -- RX State Machine: Receives 5 bytes of response data and updates outputs
    -- Protocol: X_low(1) + X_high(1) + Y_low(1) + Y_high(1) + Buttons(1) = 5 bytes total
    RX : PROCESS (aclk)
    BEGIN
        IF rising_edge(aclk) THEN

            IF aresetn = '0' THEN
                -- Reset: Start waiting for X-axis low byte
                rx_state <= JSTK_X_LOW;
                rx_cache <= (OTHERS => '0');

            ELSE

                CASE rx_state IS

                    WHEN JSTK_X_LOW =>
                        -- Receive X-axis low byte (bits 7:0)
                        IF s_axis_tvalid = '1' THEN
                            rx_cache <= s_axis_tdata;      -- Store low byte temporarily
                            rx_state <= JSTK_X_HIGH;       -- Wait for high byte
                        END IF;

                    WHEN JSTK_X_HIGH =>
                        -- Receive X-axis high byte (bits 9:8) and assemble complete X value
                        IF s_axis_tvalid = '1' THEN
                            -- Combine: high_byte(1:0) & low_byte(7:0) = 10-bit X position
                            jstk_x <= s_axis_tdata(1 DOWNTO 0) & rx_cache;
                            rx_state <= JSTK_Y_LOW;        -- Move to Y-axis reception
                        END IF;

                    WHEN JSTK_Y_LOW =>
                        -- Receive Y-axis low byte (bits 7:0)
                        IF s_axis_tvalid = '1' THEN
                            rx_cache <= s_axis_tdata;      -- Store low byte temporarily
                            rx_state <= JSTK_Y_HIGH;       -- Wait for high byte
                        END IF;

                    WHEN JSTK_Y_HIGH =>
                        -- Receive Y-axis high byte (bits 9:8) and assemble complete Y value
                        IF s_axis_tvalid = '1' THEN
                            -- Combine: high_byte(1:0) & low_byte(7:0) = 10-bit Y position
                            jstk_y <= s_axis_tdata(1 DOWNTO 0) & rx_cache;
                            rx_state <= BUTTONS;           -- Move to button reception
                        END IF;

                    WHEN BUTTONS =>
                        -- Receive button states byte
                        IF s_axis_tvalid = '1' THEN
                            btn_jstk <= s_axis_tdata(0);   -- Joystick button (bit 0)
                            btn_trigger <= s_axis_tdata(1); -- Trigger button (bit 1)
                            rx_state <= JSTK_X_LOW;        -- Return to start for next packet
                        END IF;

                END CASE;
            END IF;
        END IF;
    END PROCESS RX;

END ARCHITECTURE;