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, 6, 8, 16, 32, 64, 128, 256);
    DIVISORS_LOG2    : natural := 3;
    CSN_PULSE_CYCLES : natural := 1
  );

  port (
    -- IOs
    sck_io               : inout std_logic;
    miso_io              : inout std_logic;
    mosi_io              : inout std_logic;
    csn_io               : inout 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;
    -- 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           : out   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           : out   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_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_clock_rising : 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;

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,
      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     => master_clock_rising,
      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,
      clock_rising_o   => master_clock_rising,
      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_io,
      csn_i            => csn_io,
      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
      -- Parallel
      data_i => tx_data_i,
      data_o => rx_data_o,
      -- Serial
      sd_i => rx_serial_data,
      sd_o => open);

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

  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);

  master_connection : entity work.spi_multiplexor
    port map (
      en_i                => master_en,
      mosi_en_i           => master_mosi_en,
      miso_en_i           => master_miso_en,
      sck_en_i            => master_sck_en,
      csn_en_i            => master_csn_en,
      mosi_i              => tx_serial_data(0),
      miso_i              => '0',
      sck_i               => master_sck,
      csn_i               => master_csn,
      mosi_o              => mosi_io,
      miso_o              => miso_io,
      sck_o               => sck_io,
      csn_o               => csn_io,
      rx_valid_i          => master_rx_valid,
      rx_valid_o          => rx_valid_o,
      tx_ready_i          => master_tx_ready,
      tx_ready_o          => tx_ready_o,
      busy_i              => master_busy,
      busy_o              => busy_o,
      err_lost_rx_data_i  => master_err_lost_rx_data,
      err_lost_rx_data_o  => err_lost_rx_data_o,
      rst_in              => master_ctrl_rst_n,
      rst_on              => ctrl_rst_n,
      latch_tx_data_i     => master_latch_new_tx_data,
      latch_tx_data_o     => latch_new_tx_data,
      latch_sample_data_i => master_latch_sample_data,
      latch_sample_data_o => latch_sample_data,
      latch_change_data_i => master_latch_change_data,
      latch_change_data_o => latch_change_data_out,
      rx_serial_i         => miso_io,
      rx_serial_o         => rx_serial_data);

  slave_connection : entity work.spi_multiplexor
    port map (
      en_i                => slave_en,
      mosi_en_i           => slave_mosi_en,
      miso_en_i           => slave_miso_en,
      sck_en_i            => slave_sck_en,
      csn_en_i            => slave_csn_en,
      mosi_i              => '0',
      miso_i              => tx_serial_data(0),
      sck_i               => '0',
      csn_i               => '0',
      mosi_o              => mosi_io,
      miso_o              => miso_io,
      sck_o               => sck_io,
      csn_o               => csn_io,
      rx_valid_i          => slave_rx_valid,
      rx_valid_o          => rx_valid_o,
      tx_ready_i          => slave_tx_ready,
      tx_ready_o          => tx_ready_o,
      busy_i              => slave_busy,
      busy_o              => busy_o,
      err_lost_rx_data_i  => slave_err_lost_rx_data,
      err_lost_rx_data_o  => err_lost_rx_data_o,
      rst_in              => slave_ctrl_rst,
      rst_on              => ctrl_rst_n,
      latch_tx_data_i     => slave_latch_new_tx_data,
      latch_tx_data_o     => latch_new_tx_data,
      latch_sample_data_i => slave_latch_sample_data,
      latch_sample_data_o => latch_sample_data,
      latch_change_data_i => slave_latch_change_data,
      latch_change_data_o => latch_change_data_out,
      rx_serial_i         => mosi_io,
      rx_serial_o         => rx_serial_data);

end architecture a1;
