library ieee;
use ieee.std_logic_1164.all;
use work.spi_pkg.all;

entity spi_peripheral is

  generic (
    SIZES            : natural_vector := (8, 16);
    SIZES_2LOG       : natural := 1;
    DIVISORS         : natural_vector := (2, 4, 6, 8, 16, 32, 64, 128, 256);
    DIVISORS_LOG2    : natural := 3;
    CSN_PULSE_CYCLES : natural := 1
  );

  port (
    address_i : in std_logic_vector(2 downto 0);
    data_i : in std_logic_vector(31 downto 0);
    data_o : out std_logic_vector(31 downto 0);
    write_i : in std_logic;
    read_i : in std_logic;

    -- IOs
    sck_o                : out std_logic;
    miso_i               : in  std_logic;
    mosi_o               : out std_logic;
    csn_o                : out std_logic;

    -- Control
    clk_i                : in  std_logic;
    rst_in               : in  std_logic;
    rx_data_o            : out std_logic_vector(get_max_natural(SIZES) - 1 downto 0);
    rx_valid_o           : out std_logic;
    tx_ready_o           : out std_logic;
    busy_o               : out std_logic;
    err_lost_rx_data_o   : out std_logic);

end entity spi_peripheral;

architecture a1 of spi_peripheral is
  constant MAX_SIZE : natural := get_max_natural(SIZES);

  constant CTRL_ADDRESS   : std_logic_vector(2 downto 0) := "000";
  constant STATUS_ADDRESS : std_logic_vector(2 downto 0) := "010";
  constant DATA_ADDRESS   : std_logic_vector(2 downto 0) := "011";

  signal rx_buffer : std_logic_vector(MAX_SIZE - 1 downto 0);
  signal rx_buffer_full : std_logic;
  signal tx_buffer : std_logic_vector(MAX_SIZE - 1 downto 0);
  signal tx_buffer_full : std_logic;

  signal rx_ready : std_logic;
  signal rx_valid : std_logic;
  signal tx_valid : std_logic;
  signal tx_ready : std_logic;
  signal tx_data : std_logic_vector(MAX_SIZE - 1 downto 0);
  signal rx_data : std_logic_vector(MAX_SIZE - 1 downto 0);
  signal rx_data_lost : std_logic;
  signal busy : std_logic;

  signal reg_control : std_logic_vector(31 downto 0);
  signal reg_status : std_logic_vector(31 downto 0);

  signal clear_rx_buffer_full : std_logic;
begin  -- architecture a1

  reader: process (clk_i) is
  begin  -- process reader
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_in = '0' then              -- synchronous reset (active low)

      elsif read_i = '1' then
        case address_i is
          when DATA_ADDRESS => clear_rx_buffer_full <= '1';
          when others => null;
        end case;
      else
        clear_rx_buffer_full <= '0';
      end if;
    end if;
  end process reader;

  output_register: process (all) is
  begin  -- process output_register
    case address_i is
      when CTRL_ADDRESS => data_o(reg_control'range) <= reg_control(reg_control'range);
      when STATUS_ADDRESS => data_o <= reg_status;
      when DATA_ADDRESS => data_o <= rx_buffer;
      when others => null;
    end case;
  end process output_register;

  rx_buffer_reader: process (clk_i) is
  begin  -- process rx_buffer_full
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_in = '0' then              -- synchronous reset (active low)
        rx_buffer_full <= '0';
      else
        if clear_rx_buffer_full = '1' then
          rx_buffer_full <= '0';
        end if;

        if rx_valid = '1' then
          rx_buffer_full <= '1';
          rx_buffer <= rx_data;
        end if;
      end if;
    end if;
  end process rx_buffer_reader;

  reg_status <= "000000000000000000000000000" &
                rx_data_lost &
                busy &
                "0" &
                tx_buffer_full &
                rx_buffer_full;

  tx_data <= tx_buffer;

  rx_ready <= not rx_buffer_full;
  tx_valid <= tx_buffer_full;

end architecture a1;
