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

  signal curr_running : std_logic;
  signal next_running : std_logic;

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

  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;

  signal next_sample_data : std_logic;
  signal curr_sample_data : std_logic;

  signal next_change_data : std_logic;
  signal curr_change_data : 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_running <= '0';
        curr_sck <= '0';
        curr_counter <= 0;
        curr_sample_data <= '0';
        curr_change_data <= '0';
      else
        curr_running <= next_running;
        curr_sck <= next_sck;
        curr_counter <= next_counter;
        curr_sample_data <= next_sample_data;
        curr_change_data <= next_change_data;
      end if;
    end if;
  end process set_data;

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

  changing <= '1' when curr_counter = 0 and curr_running = '1' else '0';

  next_counter <= selected_divisor - 1 when changing = '1' else
                  0 when curr_counter = 0 else
                  curr_counter - 1 when curr_running = '1' else
                  selected_divisor - 1;

  next_sample_data <= '1' when curr_sck = clock_phase_i and changing = '1' else '0';
  next_change_data <= '1' when curr_sck /= clock_phase_i and changing = '1' else '0';

  next_sck <= not curr_sck when changing = '1'
              else curr_sck when curr_running = '1' else
              clock_polarity_i;

  next_running <= start_i;

  sck_o <= clock_polarity_i when sck_mask_i = '0' else
           curr_sck when clock_polarity_i = '0' else
           not curr_sck;

  clock_rising_o <= curr_sample_data;
  sample_data_o <= curr_sample_data and sck_mask_i;
  change_data_o <= curr_change_data and sck_mask_i;

end architecture a1;
