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

entity i2C_master is  
  generic (
    system_frequency   : real := 50.0E6;   -- 50 MHz
	  i2c_rate           : real := 20.0E3    -- 20 kHz	
  );
  port (
    clk     : in    std_logic;
	  resetn  : in    std_logic;
    go      : in    std_logic;
   	ready   : out   std_logic;
	  data_in : in    std_logic_vector(23 downto 0); -- adress & command & data
	  ack     : out   std_logic;
	  -- i2c 
	  i2c_scl : out   std_logic;              -- A6
	  i2c_sda : inout std_logic               -- B6
  );
end entity;

architecture rtl of i2c_master is
  constant mod_ctr_tempo : positive := integer(system_frequency/(i2c_rate));
  constant mod_ctr_bit   : positive := 35;
  type state is (wait_for_go, wait_for_i2clk, tx, wait_for_not_go);
  signal current_state : state; 
  signal next_state    : state;  
  signal sdo           : std_logic;  
  signal ctr_tempo     : natural range 0 to mod_ctr_tempo-1;
  signal end_tempo     : std_logic;
  signal ctr_bit       : natural range 0 to mod_ctr_bit-1;
  signal ack1          : std_logic;
  signal ack2          : std_logic;
  signal ack3          : std_logic;
  signal reg_data      : std_logic_vector(data_in'range);
  signal cmd_data      : std_logic_vector(1 downto 0);
  signal cmd_bit       : std_logic_vector(1 downto 0);
  signal cmd_ack1      : std_logic_vector(1 downto 0);
  signal cmd_ack2      : std_logic_vector(1 downto 0);
  signal cmd_ack3      : std_logic_vector(1 downto 0);
  signal sclk          : std_logic;  
  signal i2c_clk       : std_logic;
  signal ready_i       : std_logic;
  
begin
 i2c_sda   <= 'Z'     when sdo = '1' 
              else '0';
 i2c_scl   <= '1' when sclk='1' 
              else  not i2c_clk when (ctr_bit>=3 and ctr_bit <= 30)
              else '0';
 ack       <= ack1 or ack2 or ack3;	
 end_tempo <= '0' when ctr_tempo< mod_ctr_tempo-1 else '1';
 
 process(resetn, clk) is
 begin
   if resetn = '0'        then 
	  i2c_clk   <= '0';
	  ctr_tempo <= 0;
	  ctr_bit   <= 0;
	  reg_data  <= (others => '0');
	  --sclk      <= '1';
	  ACK1      <= '0';
	  ACK2      <= '0';
	  ACK3      <= '0'; 
	  ready     <= '1';
	  current_state <= wait_for_go;
  elsif rising_edge(clk) then
    current_state <= next_state;
    ready         <= ready_i;
    
    -- temporisation et i2_clk
    if current_state = wait_for_go then
       ctr_tempo <= 0;
       i2c_clk   <= '0';
    elsif end_tempo='0' then 
  	    ctr_tempo <= ctr_tempo +1;
  	 else    
  	    ctr_tempo <= 0;
  		   i2c_clk   <= not i2c_clk;
  	 end if;	 
  	 -- reg_data
  	 case cmd_data is
	    when "10"   => reg_data <= data_in;	                  
	    when "11"   => reg_data <= reg_data(22 downto 0)&'0';
	    when others => null;  
	  end case;
    --  ctr_bit
    case cmd_bit is
      when "10"   => ctr_bit <= 0;	                  
	    when "11"   =>  if ctr_bit<mod_ctr_bit-1 then ctr_bit       <= ctr_bit+1;
                      else                          ctr_bit       <= 0;
                      end if;
	    when others => null;  
    end case;
   -- ack1
    case cmd_ack1 is
	    when "10"   => ack1 <= '0';	                  
	    when "11"   => ack1 <= '1';
	    when others => null;  
	  end case;
	 -- ack2
    case cmd_ack2 is
	    when "10"   => ack2 <= '0';	                  
	    when "11"   => ack2 <= '1';
	    when others => null;  
	  end case;
	  -- ack1
    case cmd_ack3 is
	    when "10"   => ack3 <= '0';	                  
	    when "11"   => ack3 <= '1';
	    when others => null;  
	  end case;
  end if;
end process;	   
--------------------------------
-- FSM
-------------------------------
process(current_state, end_tempo, i2c_clk, go,reg_data(23), i2c_sda,ctr_bit) is
begin
  next_state <= current_state;
  ready_i    <= '0';
  cmd_ack1   <= "00";
  cmd_ack2   <= "00";
  cmd_ack3   <= "00";
  cmd_bit    <= "00";
  cmd_data   <= "00";
  sdo        <= '1';
  sclk       <= '0';
  case current_state is
    when wait_for_not_go =>  if go = '0' then next_state <=wait_for_go; 
                           end if;
                           ready_i <= '1';                           
                           cmd_bit    <= "10";
                           cmd_data   <= "10";
                           sclk       <= '1';
    when wait_for_go    => if go = '1' then next_state <=wait_for_i2clk; 
                           end if;
                           ready_i <= '1';                           
                           cmd_bit    <= "10";
                           cmd_data   <= "10";
                           sclk       <= '1';
    when wait_for_i2clk => if end_tempo='1' then  next_state <=tx;   cmd_bit    <= "11"; 
                           end if;
                           cmd_ack1   <= "10";
                           cmd_ack2   <= "10";
                           cmd_ack3   <= "10";
                           sclk       <= '1';
    when tx             => if ctr_bit< mod_ctr_bit-1 then
                               if i2c_clk='0' and end_tempo='1'then                              
                                cmd_bit    <= "11";  
                               end if;                                
                           elsif end_tempo='1'then                                 
                                next_state <= wait_for_go;                                
                          end if;      
                          case ctr_bit is 
                             when      0 => sdo  <= '1'; sclk <= '1';
                          			-- start
                          			when      1 => sdo  <= '0'; sclk <= '1';
                          			when      2 => sdo  <= '0'; sclk <= '0';
                          			-- slave address
                          			when      3 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when      4 => sdo  <= reg_data(23);
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when      5 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when      6 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when      7 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when      8 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when      9 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     10 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     11 => sdo  <= '1';         -- ack                                              
                                               if i2c_clk='0' and end_tempo='1' then
                                                if i2c_sda='0' then cmd_ack1 <= "10" ;
                          			                else                cmd_ack1 <= "11" ;
                                                end if;
                        			                 end if;
                                               -- cmd_ack1 <= "11" ;
                          			-- command
                          			when     12 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;                                               
                          			             
                          			when     13 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     14 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     15 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     16 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     17 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     18 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     19 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     20 => sdo  <= '1';         -- ack
                                               if i2c_clk='0' and end_tempo='1' then
                                                if i2c_sda='0' then cmd_ack2 <= "10" ;
                                                else                cmd_ack2 <= "11" ;
                                                end if;
                        			                 end if;
                                               -- cmd_ack2 <= "11" ;
                          			-- data
                          			when     21 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;                          			                                           			
                        			  when     22 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     23 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     24 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     25 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     26 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     27 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     28 => sdo  <= reg_data(23); 
                          			               if i2c_clk='0' and end_tempo='1'then cmd_data   <= "11";
                        			                 end if;
                          			when     29 => sdo  <= '1';         -- ack
                                               if i2c_clk='0' and end_tempo='1' then 
                                                if i2c_sda='0' then cmd_ack3 <= "10" ;
                                                else                 cmd_ack3 <= "11" ;
                                                end if;
                        			                 end if; 
                                               -- cmd_ack3 <= "11" ;
                          			--stop
                          			when     30 => sdo  <= '0'; 
                          			               
                        			                 sclk <= '0';
                          			when     31 => sdo  <= '0'; sclk <= '1';
                      			    when others => sdo  <= '1'; sclk <= '1';   ready_i <= '1';  				 
                          end case;                               
                              
                            
  end case;
    
end process;
     
end architecture;