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

library work;


entity master is

generic
(
 CLK_FREQ: integer;
 ENABLE_ENDAT: boolean;
 ENABLE_BISS: boolean;
 ENABLE_SSI: boolean
);
port
(
 -- local clock
 clk: in std_logic;
 rst: in std_logic;

 -- generated clock for slave
 -- fdiv divides CLK_FREQ
 ma_fdiv: in unsigned;
 ma_clk: out std_logic;

 -- master out, slave in
 mosi: out std_logic;
 miso: in std_logic;

 -- gate to drive output (1 to drive it)
 gate: out std_logic;

 -- data from slave, and data length
 data: out std_logic_vector;
 len: in unsigned;

 -- encoder type
 enc_type: in integer;

 -- ssi specific control registers
 ssi_flags: in std_logic_vector;
 ssi_delay_fdiv: in unsigned
);

end entity;


architecture absenc_master_rtl of master is

constant DATA_LEN: integer := data'length;

--
-- conversion pipeline

type conv_state_t is
(
 CONV_WAIT_LATCH,
 CONV_LSB_TO_MSB,
 CONV_GRAY_TO_BIN,
 CONV_EXTEND_SIGN,
 CONV_DONE
);

constant CONV_ERR: conv_state_t := CONV_WAIT_LATCH;

signal curr_state: conv_state_t;
signal next_state: conv_state_t;

signal latched_data: std_logic_vector(data'range);
signal msb_data: std_logic_vector(data'range);
signal bin_data: std_logic_vector(data'range);
signal signed_data: std_logic_vector(data'range);
signal conv_data: std_logic_vector(data'range);
signal len_mask: std_logic_vector(data'range);

--
-- enable conversion stages

signal gray_to_bin_en: std_logic;
signal lsb_to_msb_en: std_logic;

--
-- timeout counter

constant TM_MAX: integer := work.absenc_pkg.us_to_count
 (work.absenc_pkg.MAX_TM_US, CLK_FREQ, integer'high);

constant TM_LEN: integer :=
 work.absenc_pkg.integer_length(TM_MAX);

constant DEFAULT_TM_TOP: integer := work.absenc_pkg.us_to_count
 (work.absenc_pkg.MASTER_DEFAULT_TM_US, CLK_FREQ, TM_LEN);

signal tm_val: unsigned(TM_LEN - 1 downto 0);
signal tm_top: unsigned(tm_val'range);
signal tm_match: std_logic;

--
-- general counter

signal count_val:
 unsigned(work.absenc_pkg.integer_length(DATA_LEN) - 1 downto 0);
signal count_top: unsigned(count_val'range);
signal count_top_latched: unsigned(count_val'range);
signal count_rst: std_logic;
signal count_match: std_logic;

--
-- serial in, parallel out (SIPO) register

signal sipo_val: std_logic_vector(DATA_LEN - 1 downto 0);
signal sipo_latch_redge: std_logic;
signal sipo_latch_pre: std_logic;
signal sipo_latch: std_logic;

--
-- master clock

signal ma_clk_rst_en: std_logic;
signal ma_clk_rst_val: std_logic;

signal ma_clk_val: std_logic;

signal ma_half_fdiv: unsigned(ma_fdiv'length - 1 downto 0);
signal ma_half_count: unsigned(ma_half_fdiv'length - 1 downto 0);
signal ma_half_match: std_logic;

--
-- master clock rising falling edges

signal ma_clk_redge: std_logic;
signal ma_clk_fedge: std_logic;
signal ma_clk_edge: std_logic;

--
-- encoder multiplexed signal
--
-- the mux size depends on ENABLE_xxx generics. the idea is to eliminate
-- unneeded FPGA logic by instantiating only modules enabled by the generics.
-- to do so, we have functions that translate from ENC_TYPE_xxx to ENC_MUX_xxx
-- and vice versa. ENC_TYPE_xxx represent all the possible encoders, while
-- ENC_MUX_xxx represent an encoder index in the mux, if enabled by generics.

subtype tm_val_t is unsigned(tm_val'range);
type tm_val_array_t is array(integer range<>) of tm_val_t;

subtype count_val_t is unsigned(count_val'range);
type count_val_array_t is array(integer range<>) of count_val_t;

constant enc_mux_to_type: work.absenc_pkg.enc_type_array_t :=
 work.absenc_pkg.gen_enc_mux_to_type(ENABLE_ENDAT, ENABLE_BISS, ENABLE_SSI);

constant ENC_MUX_COUNT: integer :=
 work.absenc_pkg.get_enc_mux_count(ENABLE_ENDAT, ENABLE_BISS, ENABLE_SSI);

constant ENC_MUX_ENDAT: integer :=
 work.absenc_pkg.get_enc_mux_endat(ENABLE_ENDAT, ENABLE_BISS, ENABLE_SSI);

constant ENC_MUX_BISS: integer :=
 work.absenc_pkg.get_enc_mux_biss(ENABLE_ENDAT, ENABLE_BISS, ENABLE_SSI);

constant ENC_MUX_SSI: integer :=
 work.absenc_pkg.get_enc_mux_ssi(ENABLE_ENDAT, ENABLE_BISS, ENABLE_SSI);

signal ma_clk_edge_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal ma_clk_rst_en_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal ma_clk_rst_val_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal mosi_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal gate_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal tm_top_mux: tm_val_array_t(ENC_MUX_COUNT - 1 downto 0);
signal count_rst_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal count_top_mux: count_val_array_t(ENC_MUX_COUNT - 1 downto 0);
signal sipo_latch_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal gray_to_bin_en_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);
signal lsb_to_msb_en_mux: std_logic_vector(ENC_MUX_COUNT - 1 downto 0);


begin


--
-- assert lengths to reduce generated logic
-- ie. counter and comparator sizes

assert (ma_fdiv'length <= 16)
report "ma_fdiv'length too large" severity failure;


--
-- master clock generation
-- master clock frequency is clk divided by ma_fdiv
-- generate edges using a counter from ma_fdiv/2 to 0

ma_half_fdiv <= ma_fdiv srl 1;
ma_half_match <= '1' when ma_half_count = 1 else '0';

process
begin
 wait until rising_edge(clk);

 if ((rst or ma_clk_rst_en or ma_half_match) = '1') then
  ma_half_count <= ma_half_fdiv;
 else
  ma_half_count <= ma_half_count - 1;
 end if;

end process;


process
begin
 wait until rising_edge(clk);

 if ((rst or ma_clk_rst_en) = '1') then
  ma_clk_val <= ma_clk_rst_val;
 else
  ma_clk_val <= ma_clk_val xor ma_half_match;
 end if;

end process;

ma_clk <= ma_clk_val;


--
-- master clock edges

process
begin
 wait until rising_edge(clk);
 ma_clk_redge <= ma_half_match and (not ma_clk_val);
end process;

process
begin
 wait until rising_edge(clk);
 ma_clk_fedge <= ma_half_match and ma_clk_val;
end process;


--
-- timeout counter
-- tm_val decrement from tm_top to 0
-- tm_val reloaded at ma_clk_edge

process
begin
 wait until rising_edge(clk);

 tm_match <= '0';

 if ((rst or ma_clk_edge) = '1') then
  tm_val <= tm_top;
 elsif (tm_val = 0) then
  tm_val <= tm_top;
  tm_match <= '1';
 else
  tm_val <= tm_val - 1;
 end if;

end process;


--
-- general purpose counter
-- monotically incrementing
-- increment every master edge
-- starts from 1

process
begin

 wait until rising_edge(clk);

 if (count_rst = '1') then
  count_val <= to_unsigned(integer(1), count_val'length);
 elsif (ma_clk_edge = '1') then
  count_val <= count_val + 1;
 end if;

end process;


--
-- general purpose counter comparator
-- count_match set to one when count_val = count_top

process
begin

 wait until rising_edge(clk);

 if (count_rst = '1') then
  count_top_latched <= count_top;
 end if;

end process;

count_match <= '1' when (count_val = count_top_latched) else '0';


--
-- sipo register

process
begin

 wait until rising_edge(clk);

 if (ma_clk_edge = '1') then
  sipo_val <= sipo_val(sipo_val'length - 2 downto 0) & miso;
 end if;

end process;


--
-- sipo_latch edge detector
-- ma_clk_redge is not used as ma_clk may be disabled.
-- instead, clk is used for detecting sipo_latch edges

process
begin
 wait until rising_edge(clk);
 sipo_latch_pre <= sipo_latch;
end process;

sipo_latch_redge <= (not sipo_latch_pre) and sipo_latch;


--
-- data conversion pipeline. ordering:
-- data from slave (latched at sipo_latch redge from sipo_val)
-- lsb_to_msb
-- gray_to_bin
-- extend_sign
-- latched to data
-- bin_to_sfixed (implemented in top)

process
begin
 wait until rising_edge(clk);

 if (rst = '1') then
  curr_state <= CONV_WAIT_LATCH;
 else
  curr_state <= next_state;
 end if;

end process;


process(curr_state, sipo_latch_redge)
begin
 
 next_state <= curr_state;

 case curr_state is

  when CONV_WAIT_LATCH =>
   if (sipo_latch_redge = '1') then
    next_state <= CONV_LSB_TO_MSB;
   end if;

  when CONV_LSB_TO_MSB =>
   next_state <= CONV_GRAY_TO_BIN;

  when CONV_GRAY_TO_BIN =>
   next_state <= CONV_EXTEND_SIGN;

  when CONV_EXTEND_SIGN =>
   next_state <= CONV_DONE;

  when CONV_DONE =>
   next_state <= CONV_WAIT_LATCH;

  when others =>
   next_state <= CONV_ERR;

 end case;

end process;


process
begin
 wait until rising_edge(clk);

 latched_data <= latched_data;
 conv_data <= conv_data;

 case curr_state is

  when CONV_WAIT_LATCH =>
   latched_data <= sipo_val and len_mask;

  when CONV_LSB_TO_MSB =>

  when CONV_EXTEND_SIGN =>

  when CONV_DONE =>
   conv_data <= signed_data;

  when others =>

 end case;

end process;

data <= conv_data;


len_to_mask: work.absenc_pkg.len_to_mask
port map
(
 len => len,
 mask => len_mask
);


lsb_to_msb: work.absenc_pkg.lsb_to_msb
port map
(
 en => lsb_to_msb_en,
 data_len => len,
 lsb_data => latched_data,
 msb_data => msb_data
);


gray_to_bin: work.absenc_pkg.gray_to_bin
port map
(
 en => gray_to_bin_en,
 gray_data => msb_data,
 bin_data => bin_data
);


extend_sign: work.absenc_pkg.extend_sign
port map
(
 data_len => len,
 data_in => bin_data,
 data_out => signed_data,
 len_mask => len_mask
);


--
-- encoder master implementations

gen_endat: if ENABLE_ENDAT = TRUE generate
master_endat: work.absenc_pkg.master_endat
generic map
(
 CLK_FREQ => CLK_FREQ
)
port map
(
 clk => clk,
 rst => rst,
 ma_clk_fedge => ma_clk_fedge,
 ma_clk_redge => ma_clk_redge,
 ma_clk_edge => ma_clk_edge_mux(ENC_MUX_ENDAT),
 ma_clk_rst_en => ma_clk_rst_en_mux(ENC_MUX_ENDAT),
 ma_clk_rst_val => ma_clk_rst_val_mux(ENC_MUX_ENDAT),
 mosi => mosi_mux(ENC_MUX_ENDAT),
 miso => miso,
 gate => gate_mux(ENC_MUX_ENDAT),
 len => len,
 tm_match => tm_match,
 tm_top => tm_top_mux(ENC_MUX_ENDAT),
 count_top => count_top_mux(ENC_MUX_ENDAT),
 count_match => count_match,
 count_rst => count_rst_mux(ENC_MUX_ENDAT),
 sipo_val => sipo_val,
 sipo_latch => sipo_latch_mux(ENC_MUX_ENDAT),
 gray_to_bin_en => gray_to_bin_en_mux(ENC_MUX_ENDAT),
 lsb_to_msb_en => lsb_to_msb_en_mux(ENC_MUX_ENDAT)
);
end generate gen_endat;


gen_biss: if ENABLE_BISS = TRUE generate
master_biss: work.absenc_pkg.master_biss
generic map
(
 CLK_FREQ => CLK_FREQ
)
port map
(
 clk => clk,
 rst => rst,
 ma_clk_fedge => ma_clk_fedge,
 ma_clk_redge => ma_clk_redge,
 ma_clk_edge => ma_clk_edge_mux(ENC_MUX_BISS),
 ma_clk_rst_en => ma_clk_rst_en_mux(ENC_MUX_BISS),
 ma_clk_rst_val => ma_clk_rst_val_mux(ENC_MUX_BISS),
 mosi => mosi_mux(ENC_MUX_BISS),
 miso => miso,
 gate => gate_mux(ENC_MUX_BISS),
 len => len,
 tm_match => tm_match,
 tm_top => tm_top_mux(ENC_MUX_BISS),
 count_top => count_top_mux(ENC_MUX_BISS),
 count_match => count_match,
 count_rst => count_rst_mux(ENC_MUX_BISS),
 sipo_val => sipo_val,
 sipo_latch => sipo_latch_mux(ENC_MUX_BISS),
 gray_to_bin_en => gray_to_bin_en_mux(ENC_MUX_BISS),
 lsb_to_msb_en => lsb_to_msb_en_mux(ENC_MUX_BISS)
);
end generate gen_biss;


gen_ssi: if ENABLE_SSI = TRUE generate
master_ssi: work.absenc_pkg.master_ssi
generic map
(
 CLK_FREQ => CLK_FREQ
)
port map
(
 clk => clk,
 rst => rst,
 ma_clk_fedge => ma_clk_fedge,
 ma_clk_redge => ma_clk_redge,
 ma_clk_edge => ma_clk_edge_mux(ENC_MUX_SSI),
 ma_clk_rst_en => ma_clk_rst_en_mux(ENC_MUX_SSI),
 ma_clk_rst_val => ma_clk_rst_val_mux(ENC_MUX_SSI),
 mosi => mosi_mux(ENC_MUX_SSI),
 miso => miso,
 gate => gate_mux(ENC_MUX_SSI),
 len => len,
 tm_match => tm_match,
 tm_top => tm_top_mux(ENC_MUX_SSI),
 count_top => count_top_mux(ENC_MUX_SSI),
 count_match => count_match,
 count_rst => count_rst_mux(ENC_MUX_SSI),
 sipo_val => sipo_val,
 sipo_latch => sipo_latch_mux(ENC_MUX_SSI),
 gray_to_bin_en => gray_to_bin_en_mux(ENC_MUX_SSI),
 lsb_to_msb_en => lsb_to_msb_en_mux(ENC_MUX_SSI),
 ssi_flags => ssi_flags,
 ssi_delay_fdiv => ssi_delay_fdiv
);
end generate gen_ssi;


--
-- enc_type multiplexer

process
(
 enc_type,
 ma_clk_edge_mux,
 ma_clk_rst_en_mux,
 ma_clk_rst_val_mux,
 mosi_mux,
 gate_mux,
 tm_top_mux,
 count_rst_mux,
 count_top_mux,
 sipo_latch_mux,
 gray_to_bin_en_mux,
 lsb_to_msb_en_mux
)

begin

 ma_clk_edge <= ma_clk_redge;
 ma_clk_rst_en <= '0';
 ma_clk_rst_val <= '1';
 mosi <= '0';
 gate <= '0';
 tm_top <= to_unsigned(DEFAULT_TM_TOP, tm_top'length);
 count_rst <= '0';
 count_top <= (others => '0');
 sipo_latch <= '0';
 gray_to_bin_en <= '0';
 lsb_to_msb_en <= '0';

 for i in 0 to ENC_MUX_COUNT - 1 loop
  if enc_mux_to_type(i) = enc_type then
   ma_clk_edge <= ma_clk_edge_mux(i);
   ma_clk_rst_en <= ma_clk_rst_en_mux(i);
   ma_clk_rst_val <= ma_clk_rst_val_mux(i);
   mosi <= mosi_mux(i);
   gate <= gate_mux(i);
   tm_top <= tm_top_mux(i);
   count_rst <= count_rst_mux(i);
   count_top <= count_top_mux(i);
   sipo_latch <= sipo_latch_mux(i);
   gray_to_bin_en <= gray_to_bin_en_mux(i);
   lsb_to_msb_en <= lsb_to_msb_en_mux(i);
  end if;
 end loop;

end process;


end absenc_master_rtl;