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 (
    waddress_i         : in    std_logic_vector(2 downto 0);
    wdata_i            : in    std_logic_vector(31 downto 0);
    raddress_i         : in    std_logic_vector(2 downto 0);
    rdata_o            : out   std_logic_vector(31 downto 0);
    write_i            : in    std_logic;
    read_i             : in    std_logic;
    -- IOs
    sck_i                : in  std_logic;
    miso_i               : in  std_logic;
    mosi_i               : in  std_logic;
    csn_i                : in  std_logic;
    sck_o                : out std_logic;
    miso_o               : out std_logic;
    mosi_o               : out std_logic;
    csn_o                : out std_logic;
    sck_t                : out std_logic;
    miso_t               : out std_logic;
    mosi_t               : out std_logic;
    csn_t                : out std_logic;
    -- Control
    clk_i              : in    std_logic;
    rst_in             : in    std_logic;
    interrupt_o        : out   std_logic
    -- 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 INTMASK_ADDRESS : std_logic_vector(2 downto 0) := "001";
  constant STATUS_ADDRESS  : std_logic_vector(2 downto 0) := "010";
  constant DATA_ADDRESS    : std_logic_vector(2 downto 0) := "011";

  constant CTRL_EN_BIT             : natural := 0;
  constant CTRL_MASTER_BIT         : natural := 1;
  constant CTRL_TX_EN_BIT          : natural := 2;
  constant CTRL_RX_EN_BIT          : natural := 3;
  constant CTRL_CLOCK_POLARITY_BIT : natural := 4;
  constant CTRL_CLOCK_PHASE_BIT    : natural := 5;
  constant CTRL_PULSE_CSN_BIT      : natural := 6;
  constant CTRL_LSBFIRST_BIT       : natural := 7;
  -- constant CTRL_RX_BLOCK_ON_FULL_BIT : natural := 8;
  constant CTRL_SIZE_SEL_BIT       : natural := 10;
  constant CTRL_DIV_SEL_BIT        : natural := 20;

  constant INTMASK_RX_BUFFER_FULL : natural := 0;
  constant INTMASK_TX_BUFFER_EMPTY : natural := 1;
  constant INTMASK_LOST_RX_DATA : natural := 4;

  constant CTRL_DEFAULT : std_logic_vector(31 downto 0)
  := (CTRL_TX_EN_BIT => '1',
      CTRL_RX_EN_BIT => '1',
      others         => '0');
  constant INTMASK_DEFAULT : std_logic_vector(31 downto 0) := (others => '0');

  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 fill_tx_buffer : std_logic;

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

  signal clear_rx_buffer_full : std_logic;

  signal en                 : std_logic;
  signal master             : std_logic;
  signal clock_polarity     : std_logic;
  signal clock_phase        : std_logic;
  signal size_sel           : std_logic_vector(SIZES_2LOG - 1 downto 0);
  signal div_sel            : std_logic_vector(DIVISORS_LOG2 - 1 downto 0);
  signal pulse_csn          : std_logic;
  signal rx_block_on_full   : std_logic;
  signal lsbfirst           : std_logic;
  -- Rx
  signal rx_en              : std_logic;
  signal rx_data            : std_logic_vector(MAX_SIZE - 1 downto 0);
  signal rx_valid           : std_logic;
  signal rx_ready           : std_logic;
  -- Tx
  signal tx_en              : std_logic;
  signal tx_data            : std_logic_vector(MAX_SIZE - 1 downto 0);
  signal tx_valid           : std_logic;
  signal tx_ready           : std_logic;
  -- State
  signal busy               : std_logic;
  signal err_lost_rx_data   : std_logic;
  signal clear_lost_rx_data : std_logic;
begin  -- architecture a1

  interrupt_o <= (rx_buffer_full and reg_intmask(INTMASK_RX_BUFFER_FULL)) or
                 (not tx_buffer_full and reg_intmask(INTMASK_TX_BUFFER_EMPTY)) or
                 (err_lost_rx_data and reg_intmask(INTMASK_LOST_RX_DATA));

  en             <= reg_control(CTRL_EN_BIT);
  master         <= reg_control(CTRL_MASTER_BIT);
  clock_polarity <= reg_control(CTRL_CLOCK_POLARITY_BIT);
  clock_phase    <= reg_control(CTRL_CLOCK_PHASE_BIT);
  pulse_csn      <= reg_control(CTRL_PULSE_CSN_BIT);
  size_sel       <= reg_control(CTRL_SIZE_SEL_BIT + SIZES_2LOG - 1 downto CTRL_SIZE_SEL_BIT);
  div_sel        <= reg_control(CTRL_DIV_SEL_BIT + DIVISORS_LOG2 - 1 downto CTRL_DIV_SEL_BIT);
  lsbfirst       <= reg_control(CTRL_LSBFIRST_BIT);

  tx_en          <= reg_control(CTRL_TX_EN_BIT);
  rx_en          <= reg_control(CTRL_RX_EN_BIT);

  rx_block_on_full <= '0';

  masterslave : entity work.spi_masterslave
    generic map (
      DIVISORS         => DIVISORS,
      DIVISORS_LOG2    => DIVISORS_LOG2,
      SIZES            => SIZES,
      SIZES_2LOG       => SIZES_2LOG,
      CSN_PULSE_CYCLES => CSN_PULSE_CYCLES)
    port map (
      -- IOs
      sck_o               => sck_o,
      miso_o              => miso_o,
      mosi_o              => mosi_o,
      csn_o               => csn_o,
      sck_i               => sck_i,
      miso_i              => miso_i,
      mosi_i              => mosi_i,
      csn_i               => csn_i,
      sck_t               => sck_t,
      miso_t              => miso_t,
      mosi_t              => mosi_t,
      csn_t               => csn_t,
      -- Control
      clk_i                => clk_i,
      rst_in               => rst_in,
      en_i                 => en,
      master_i             => master,
      clock_polarity_i     => clock_polarity,
      clock_phase_i        => clock_phase,
      size_sel_i           => size_sel,
      div_sel_i            => div_sel,
      pulse_csn_i          => pulse_csn,
      rx_block_on_full_i   => rx_block_on_full,
      lsbfirst_i           => lsbfirst,
      -- Data
      -- Rx
      rx_en_i              => rx_en,
      rx_data_o            => rx_data,
      rx_valid_o           => rx_valid,
      rx_ready_i           => rx_ready,
      -- Tx
      tx_en_i              => tx_en,
      tx_data_i            => tx_data,
      tx_valid_i           => tx_valid,
      tx_ready_o           => tx_ready,
      -- State
      busy_o               => busy,
      err_lost_rx_data_o   => err_lost_rx_data,
      clear_lost_rx_data_i => clear_lost_rx_data);

  writer: process (clk_i) is
  begin  -- process writer
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_in = '0' then              -- synchronous reset (active low)
        reg_control <= CTRL_DEFAULT;
        reg_intmask <= INTMASK_DEFAULT;
        fill_tx_buffer <= '0';
      elsif write_i = '1' then
        case waddress_i is
          when CTRL_ADDRESS => reg_control <= wdata_i;
          when INTMASK_ADDRESS => reg_intmask <= wdata_i;
          when DATA_ADDRESS => fill_tx_buffer <= '1';
          when STATUS_ADDRESS =>
            clear_lost_rx_data <= wdata_i(4);
          when others => null;
        end case;
      else
        fill_tx_buffer <= '0';
        clear_lost_rx_data <= '0';
      end if;
    end if;
  end process writer;

  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)
        clear_rx_buffer_full <= '0';
      elsif read_i = '1' then
        case raddress_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 raddress_i is
      when CTRL_ADDRESS => rdata_o <= reg_control;
      when STATUS_ADDRESS => rdata_o <= reg_status;
      when DATA_ADDRESS => rdata_o <= (15 downto 0 => rx_buffer, others => '0');
      when INTMASK_ADDRESS => rdata_o <= reg_intmask;
      when others => rdata_o <= (others => '0');
    end case;
  end process output_register;

  tx_buffer_writer: process (clk_i) is
  begin  -- process tx_buffer_writer
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_in = '0' then              -- synchronous reset (active low)
        tx_buffer <= (others => '0');
        tx_buffer_full <= '0';
      else
        if fill_tx_buffer = '1' and tx_buffer_full =  '0' then
          tx_buffer_full <= '1';
          tx_buffer <= wdata_i(tx_buffer'range);
          tx_buffer_full <= '1';
        elsif tx_ready = '1' and tx_buffer_full = '1' then
          tx_buffer_full <= '0';
        end if;
      end if;
    end if;
  end process tx_buffer_writer;

  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" &
                err_lost_rx_data &
                busy &
                "0" &
                (not tx_buffer_full) &
                rx_buffer_full;

  tx_data <= tx_buffer;

  rx_ready <= not rx_buffer_full;
  tx_valid <= tx_buffer_full;

end architecture a1;
