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

use work.spi_pkg.all;

entity spi_masterslave is

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

  port (
    -- 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;
    en_i                 : in  std_logic;
    master_i             : in  std_logic;
    clock_polarity_i     : in  std_logic;
    clock_phase_i        : in  std_logic;
    size_sel_i           : in  std_logic_vector(SIZES_2LOG - 1 downto 0);
    div_sel_i            : in  std_logic_vector(DIVISORS_LOG2 - 1 downto 0);
    pulse_csn_i          : in  std_logic;
    rx_block_on_full_i   : in  std_logic;
    lsbfirst_i           : in  std_logic;
    -- Data
    -- Rx
    rx_en_i              : in  std_logic;
    rx_data_o            : out std_logic_vector(get_max_natural(SIZES) - 1 downto 0);
    rx_valid_o           : out std_logic;
    rx_ready_i           : in  std_logic;
    -- Tx
    tx_en_i              : in  std_logic;
    tx_data_i            : in  std_logic_vector(get_max_natural(SIZES) - 1 downto 0);
    tx_valid_i           : in  std_logic;
    tx_ready_o           : out std_logic;
    -- State
    busy_o               : out std_logic;
    err_lost_rx_data_o   : out std_logic;
    clear_lost_rx_data_i : in  std_logic);

end entity spi_masterslave;

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

  signal rst_n : std_logic;

  signal ctrl_rst_n : std_logic;
  signal master_ctrl_rst_n : std_logic;
  signal slave_ctrl_rst_n : std_logic;

  signal latch_sample_data     : std_logic;
  signal latch_change_data_out : std_logic;
  signal latch_new_tx_data     : std_logic;

  signal tx_input_data : std_logic;
  signal tx_input_index : natural range 0 to MAX_SIZE - 1;
  signal tx_serial_data : std_logic_vector(0 downto 0);
  signal rx_serial_data : std_logic;

  signal selected_size : natural;

  signal master_en : std_logic;
  signal slave_en : std_logic;

  signal slave_clock_rising : std_logic;
  signal slave_rx_valid : std_logic;
  signal slave_tx_ready : std_logic;
  signal slave_busy : std_logic;
  signal slave_err_lost_rx_data : std_logic;
  signal slave_ctrl_rst : std_logic;
  signal slave_csn_en : std_logic;
  signal slave_mosi_en : std_logic;
  signal slave_miso_en : std_logic;
  signal slave_sck_mask : std_logic;
  signal slave_sck_en : std_logic;
  signal slave_start_clock : std_logic;
  signal slave_latch_new_tx_data : std_logic;
  signal slave_latch_sample_data : std_logic;
  signal slave_latch_change_data : std_logic;

  signal master_sck : std_logic;
  signal master_counter_overflow : std_logic;
  signal master_rx_valid : std_logic;
  signal master_tx_ready : std_logic;
  signal master_busy : std_logic;
  signal master_err_lost_rx_data : std_logic;
  signal master_csn : std_logic;
  signal master_csn_en : std_logic;
  signal master_mosi_en : std_logic;
  signal master_miso_en : std_logic;
  signal master_sck_mask : std_logic;
  signal master_sck_en : std_logic;
  signal master_start_clock : std_logic;
  signal master_latch_new_tx_data : std_logic;
  signal master_latch_sample_data : std_logic;
  signal master_latch_change_data : std_logic;

  signal csn, sck, mosi, miso : std_logic;
  signal csn_en, sck_en, mosi_en, miso_en : std_logic;

begin  -- architecture a1
  master_en <= en_i and master_i;
  slave_en <= en_i and not master_i;

  rst_n <= rst_in and ctrl_rst_n;
  selected_size <= SIZES(to_integer(unsigned(size_sel_i)));

  master_ctrl : entity work.spi_master_ctrl
    generic map (
      DIVISORS         => DIVISORS,
      DIVISORS_LOG2    => DIVISORS_LOG2,
      SIZES            => SIZES,
      SIZES_2LOG       => SIZES_2LOG,
      CSN_PULSE_CYCLES => CSN_PULSE_CYCLES)
    port map (
      clk_i                => clk_i,
      rst_in               => rst_in,
      en_i                 => master_en,
      size_sel_i           => size_sel_i,
      div_sel_i            => div_sel_i,
      pulse_csn_i          => pulse_csn_i,
      clock_phase_i        => clock_phase_i,
      rx_block_on_full_i   => rx_block_on_full_i,
      rx_en_i              => rx_en_i,
      rx_ready_i           => rx_ready_i,
      tx_en_i              => tx_en_i,
      tx_valid_i           => tx_valid_i,
      clear_lost_rx_data_i => clear_lost_rx_data_i,
      counter_overflow_i   => master_counter_overflow,
      rx_valid_o           => master_rx_valid,
      tx_ready_o           => master_tx_ready,
      busy_o               => master_busy,
      err_lost_rx_data_o   => master_err_lost_rx_data,
      rst_on               => master_ctrl_rst_n,
      csn_o                => master_csn,
      csn_en_o             => master_csn_en,
      mosi_en_o            => master_mosi_en,
      miso_en_o            => master_miso_en,
      sck_mask_o           => master_sck_mask,
      sck_en_o             => master_sck_en,
      gen_clk_en_o         => master_start_clock,
      latch_tx_data_o      => master_latch_new_tx_data);

  slave_ctrl : entity work.spi_slave_ctrl
    generic map (
      SIZES            => SIZES,
      SIZES_2LOG       => SIZES_2LOG,
      CSN_PULSE_CYCLES => CSN_PULSE_CYCLES)
    port map (
      clk_i                => clk_i,
      rst_in               => rst_in,
      en_i                 => master_en,
      size_sel_i           => size_sel_i,
      pulse_csn_i          => pulse_csn_i,
      rx_block_on_full_i   => rx_block_on_full_i,
      rx_en_i              => rx_en_i,
      rx_ready_i           => rx_ready_i,
      tx_en_i              => tx_en_i,
      tx_valid_i           => tx_valid_i,
      clear_lost_rx_data_i => clear_lost_rx_data_i,

      clock_rising_i       => slave_clock_rising,
      rx_valid_o           => slave_rx_valid,
      tx_ready_o           => slave_tx_ready,
      busy_o               => slave_busy,
      err_lost_rx_data_o   => slave_err_lost_rx_data,
      rst_on               => slave_ctrl_rst,
      csn_en_o             => slave_csn_en,
      mosi_en_o            => slave_mosi_en,
      miso_en_o            => slave_miso_en,
      sck_mask_o           => slave_sck_mask,
      sck_en_o             => slave_sck_en,
      gen_clk_en_o         => slave_start_clock,
      latch_tx_data_o      => slave_latch_new_tx_data);

  clkgen : entity work.spi_clkgen
    generic map (
      DIVISORS      => DIVISORS,
      DIVISORS_LOG2 => DIVISORS_LOG2)
    port map (
      clk_i              => clk_i,
      rst_in             => master_ctrl_rst_n,
      start_i            => master_start_clock,
      div_sel_i          => div_sel_i,
      clock_polarity_i   => clock_polarity_i,
      clock_phase_i      => clock_phase_i,
      sck_o              => master_sck,
      sck_mask_i         => master_sck_mask,
      counter_overflow_o => master_counter_overflow,
      sample_data_o      => master_latch_sample_data,
      change_data_o      => master_latch_change_data);

  clkmon : entity work.spi_clkmon
    port map (
      clk_i            => clk_i,
      rst_in           => slave_ctrl_rst_n,
      clock_polarity_i => clock_polarity_i,
      clock_phase_i    => clock_phase_i,
      sck_i            => sck_i,
      csn_i            => csn_i,
      clock_rising_o   => slave_clock_rising,
      sample_data_o    => slave_latch_sample_data,
      change_data_o    => slave_latch_change_data);

  shift_register : entity work.shift_register
    generic map (
      SIZE => MAX_SIZE)
    port map (
      clk_i      => clk_i,
      rst_in     => rst_n,
      -- Control
      shift_i    => latch_sample_data,  -- sampling
      latch_i    => latch_new_tx_data,  -- latching tx data
      lsbfirst_i => lsbfirst_i,
      -- Parallel
      data_i     => tx_data_i,
      data_o     => rx_data_o,
      -- Serial
      sd_i       => rx_serial_data,
      sd_o       => open);

  tx_input_index <= 0 when selected_size = 0 else
                    selected_size - 1 when lsbfirst_i = '0' else 0;
  tx_input_data <= tx_data_i(tx_input_index) when latch_new_tx_data = '1' and latch_change_data_out = '1' else
                   rx_data_o(tx_input_index);

  mosi_reg : entity work.reg
    generic map (
      SIZE => 1)
    port map (
      -- outputting different bit on mosi
      clk_i   => clk_i,
      rst_in  => rst_n,
      d_i     => (0 => tx_input_data),
      q_o     => tx_serial_data,
      latch_i => latch_change_data_out);

  mosi_t <= not slave_mosi_en when slave_en = '1' else
            not master_mosi_en when master_en = '1' else
            '1';
  miso_t <= not slave_miso_en when slave_en = '1' else
            not master_miso_en when master_en = '1' else
            '1';
  sck_t <= not slave_sck_en when slave_en = '1' else
           not master_sck_en when master_en = '1' else
           '1';
  csn_t <= not slave_csn_en when slave_en = '1' else
           not master_csn_en when master_en = '1' else
           '1';

  mosi_o <= tx_serial_data(0) when master_en = '1' else
          '0' when slave_en = '1' else
          '0';

  miso_o <= '0' when master_en = '1' else
          tx_serial_data(0) when slave_en = '1' else '0';

  sck_o <= master_sck when master_en = '1' else
         '0';

  csn_o <= master_csn when master_en = '1' else '0';

  rx_valid_o <= master_rx_valid when master_en = '1' else
                slave_rx_valid when slave_en = '1' else
                '0';
  tx_ready_o <= master_tx_ready when master_en = '1' else
                slave_tx_ready when slave_en = '1' else
                '0';
  busy_o <= master_busy when master_en = '1' else
                slave_busy when slave_en = '1' else
                '0';
  err_lost_rx_data_o <= master_err_lost_rx_data when master_en = '1' else
                        slave_err_lost_rx_data when slave_en = '1' else
                        '0';
  ctrl_rst_n <= master_ctrl_rst_n when master_en = '1' else
                slave_ctrl_rst_n when slave_en = '1' else
                '0';
  latch_new_tx_data <= master_latch_new_tx_data when master_en = '1' else
                       slave_latch_new_tx_data when slave_en = '1' else
                       '0';
  rx_serial_data <= miso_i when master_en =  '1' else
                    mosi_i when slave_en = '1' else
                    '0';
  latch_sample_data <= master_latch_sample_data when master_en = '1' else
                slave_latch_sample_data when slave_en = '1' else
                '0';
  latch_change_data_out <= master_latch_change_data when master_en = '1' else
                slave_latch_change_data when slave_en = '1' else
                '0';

end architecture a1;
