-------------------------------------------------------------------------------
--
-- Project name: UDP_100G
--
-- (C) Copyright 2020 DFC Design, s.r.o., Brno, Czech Republic
-- Author: Jakub Hadamek (j.hadamek@dfcdesign.cz)
--
-------------------------------------------------------------------------------
--
-- Purpose of this IP is to convert SPI to Analog Devices ADI-SPI
-- 
-------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
library UNISIM;
use UNISIM.VCOMPONENTS.ALL;

entity spi_to_3wire_spi is
   generic (
      g_slave_cnt          : natural := 1;
      g_addr_ctrl_bit_cnt  : natural range 1 to 32 := 16;
      g_rw_bit_num         : natural := 0;
      g_debug              : boolean := true
   );
   port (
      RESET                : in  std_logic;
      CLK                  : in  std_logic;
      -- SP
      SPI_SCLK             : in  std_logic;
      SPI_SS               : in  std_logic_vector(g_slave_cnt - 1 downto 0);
      SPI_MOSI             : in  std_logic;
      SPI_MISO             : out std_logic;
      -- 3 wire SPI
      CSB                  : out std_logic_vector(g_slave_cnt - 1 downto 0);
      SCLK                 : out std_logic;
      SDIO                 : inout std_logic

   );
end;

architecture behavioral of spi_to_3wire_spi is
   component debug_ila is
      port (
         clk            : in std_logic;
         probe0         : in std_logic_vector(0 downto 0);
         probe1         : in std_logic_vector(0 downto 0);
         probe2         : in std_logic_vector(0 downto 0);
         probe3         : in std_logic_vector(0 downto 0);
         probe4         : in std_logic_vector(0 downto 0);
         probe5         : in std_logic_vector(0 downto 0);
         probe6         : in std_logic_vector(0 downto 0);
         probe7         : in std_logic_vector(0 downto 0);
         probe8         : in std_logic_vector(19 downto 0)
      );   
   end component;
   
--          Vector AND
-- -----------------------------------------------------------   
   function andvec(vec  : std_logic_vector)
   return std_logic is
      variable ret      : std_logic := '1';
   begin 
      for i in 0 to vec'length-1 loop
         ret := ret and vec(i);
      end loop;
      return ret;
   end function andvec;
   
--          Vector OR
-- -----------------------------------------------------------     
   function orvec(vec  : std_logic_vector)
   return std_logic is
      variable ret      : std_logic := '0';
   begin 
      for i in 0 to vec'length-1 loop
         ret := ret or vec(i);
      end loop;
      return ret;
   end function orvec;
   
   constant LC_READ        : std_logic := '1';

   signal r_edge_cnt       : std_logic_vector(19 downto 0) := (others => '0');
   signal r_edge_cnt_zero  : std_logic := '0';
   signal f_edge_cnt       : std_logic_vector(19 downto 0) := (others => '0');
   signal r_edge           : std_logic := '0';
   signal f_edge           : std_logic := '0';
   signal spi_ss_or        : std_logic := '0';
   signal spi_sclk_d       : std_logic := '0';
   
   signal tristate_en_i    : std_logic := '0';
   signal rw               : std_logic := '0';
   
   signal spi_miso_i       : std_logic := '0';
   
begin

   spi_ss_or <= andvec(SPI_SS);
   
   rising_edge_cnt_proc: process(CLK) 
      begin 
         if RESET = '1' then
            r_edge_cnt <= (others => '0');
            spi_sclk_d <= '1';
            rw <= '0';
            tristate_en_i <= '0';
         else
            -- register spi clock value
            if rising_edge(clk) then 
               spi_sclk_d <= SPI_SCLK;
               -- check if one of chip select signals is active
               if spi_ss_or = '0' then 
               
                  -- update counter on rising edge
                  if (not spi_sclk_d and SPI_SCLK) = '1' then
                     -- register read write bit while rising edge
                     if r_edge_cnt = g_rw_bit_num then
                        rw <= SPI_MOSI;
                     end if;
                     r_edge_cnt <= r_edge_cnt + 1;
                  end if; 

                  -- enable tristate if we are in read mode
                  if (spi_sclk_d and not SPI_SCLK) = '1' and r_edge_cnt > g_addr_ctrl_bit_cnt-1 and rw = LC_READ then 
                     tristate_en_i <= '1';
                  end if;
               else
                  rw <= '0';
                  tristate_en_i <= '0';
                  r_edge_cnt <= (others => '0');
               end if;
            end if;
         end if;
      end process;



--   TRISTATE_EN <= tristate_en_i;
   SCLK <= SPI_SCLK;
   CSB <= SPI_SS;
--   SDO <= SPI_MOSI;
--   SPI_MISO <= SDI;
   
   sdio_iobuf_inst: IOBUF 
   generic map(
      SLEW        => "FAST"
    )
   port map(
     O   => spi_miso_i,
     IO  => SDIO,
     I   => SPI_MOSI,
     T   => tristate_en_i
     );

   SPI_MISO <= spi_miso_i;

   debug_ila_gen: if g_debug generate
      debug_ila_inst: debug_ila
      port map(
         clk         => CLK,
         probe0(0)   => SPI_SCLK,
         probe1(0)   => SPI_SS(0),
         probe2(0)   => SPI_MOSI,
         probe3(0)   => spi_miso_i,
         probe4(0)   => tristate_en_i,
         probe5(0)   => spi_sclk_d,
         probe6(0)   => rw,
         probe7(0)   => spi_ss_or,
         probe8      => r_edge_cnt
      );   
   end generate;
end;
