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

entity spi_clkgen is

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

  port (
    clk_i              : in  std_logic;
    rst_in             : in  std_logic;
    start_i            : in  std_logic;
    -- next_data_i      : in  std_logic;
    div_sel_i          : in  std_logic_vector(DIVISORS_LOG2 - 1 downto 0);
    clock_polarity_i   : in  std_logic;
    clock_phase_i      : in  std_logic;
    sck_o              : out std_logic;
    sck_mask_i         : in  std_logic;
    counter_overflow_o : out std_logic;
    sample_data_o      : out std_logic;
    change_data_o      : out std_logic);

end entity spi_clkgen;

architecture a1 of spi_clkgen is
  constant MAX : natural := get_max_natural(DIVISORS);

  type state_t is (IDLE, CLK_GEN, SCK_GEN);

  signal running : std_logic;

  signal selected_divisor : natural range 0 to MAX := 1;
  signal changing : std_logic;

  signal curr_state : state_t;
  signal next_state : state_t;

  signal curr_counter : integer range 0 to MAX - 1;
  signal next_counter : integer range 0 to MAX - 1;

  signal curr_sck : std_logic;
  signal next_sck : std_logic;

begin  -- architecture a1

  set_data: process (clk_i) is
  begin  -- process set_data
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_in = '0' then              -- synchronous reset (active low)
        curr_sck <= '0';
        curr_counter <= 0;
        curr_state <= IDLE;
      else
        curr_sck <= next_sck;
        curr_counter <= next_counter;
        curr_state <= next_state;
      end if;
    end if;
  end process set_data;

  state: process (all) is
  begin  -- process state
    next_state <= curr_state;
    running <= '0';

    sample_data_o <= '0';
    change_data_o <= '0';

    case curr_state is
      when IDLE =>
        if start_i = '1' then
          if sck_mask_i = '1' then
            next_state <= SCK_GEN;
            change_data_o <= not clock_phase_i;
          else
            next_state <= CLK_GEN;
          end if;
        end if;
      when CLK_GEN =>
        running <= '1';
        if start_i = '0' then
          next_state <= IDLE;
        elsif sck_mask_i = '1' then
          next_state <= SCK_GEN;
        end if;
      when SCK_GEN =>
        running <= '1';

        if changing = '1' then
          if curr_sck = clock_phase_i then
            sample_data_o <= sck_mask_i;
          else
            change_data_o <= sck_mask_i;
          end if;
        end if;

        if start_i = '0' and ((changing = '1' and curr_sck /= '0') or (curr_sck = '0')) then
          next_state <= IDLE;
          sample_data_o <= '0';
          change_data_o <= '0';
        elsif sck_mask_i =  '0' and ((changing = '1' and curr_sck /= '0') or (curr_sck = '0')) then
          next_state <= CLK_GEN;
          sample_data_o <= '0';
          change_data_o <= '0';
        end if;
      when others => null;
    end case;
  end process state;

  selected_divisor <= DIVISORS(to_integer(unsigned(div_sel_i)));

  changing <= '1' when curr_counter = 0 and running = '1' and curr_state = SCK_GEN else '0';

  next_counter <= selected_divisor - 2 when changing = '1' else
                  curr_counter - 1 when running = '1' and curr_counter /= 0 else
                  selected_divisor - 2 when selected_divisor > 1 else
                  0;

  -- sample_data_o <= '1' when curr_sck = clock_phase_i and changing = '1' else '0';
  -- change_data_o <= '1' when curr_sck /= clock_phase_i and changing = '1' else
  --                  '1' when clock_phase_i = '0' and start_i = '1' and running = '0' else
  --                  -- '1' when next_data_i = '1' else
  --                  '0';

  next_sck <= '0' when curr_state /= SCK_GEN else
              not curr_sck when changing = '1'
              else curr_sck when running = '1' else
              '0';

  sck_o <= clock_polarity_i when curr_state /= SCK_GEN else
           curr_sck when clock_polarity_i = '0' else
           not curr_sck;

  counter_overflow_o <= '1' when running = '1' and curr_counter = 0 else '0';

end architecture a1;
