-- -----------------------------------------------------------------------------
-- Copyright (c) 2013 Benjamin Krill <benjamin@krll.de>
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
--
-- Verilog to VHDL translation of:
-- http://www.eda365.com/thread-31086-1-1.html
-- -----------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity i2c_slave is
	generic (ADDRESS : std_logic_vector(6 downto 0) := "0110101"); -- 6A write, 6B read
	port (
		clk             : in  std_logic;
		rst_n           : in  std_logic;
		sda_i           : in  std_logic;
		sda_o           : out std_logic;
		scl_i           : in  std_logic;
		rd_dat_i        : in  std_logic_vector(7 downto 0);
		addr_o          : out std_logic_vector(7 downto 0);
		wr_dat_o        : out std_logic_vector(7 downto 0);
		wr_pulse_o      : out std_logic;
		rd_pulse_o      : out std_logic;
		rd_wr_o         : out std_logic
	);
end i2c_slave;

architecture i2c_slave of i2c_slave is
	signal sda_sr        : std_logic_vector(3 downto 0);
	signal scl_sr        : std_logic_vector(3 downto 0);
	signal sda, was_sda  : std_logic;
	signal sda_in        : std_logic;
	signal scl, was_scl  : std_logic;
	signal scl_in        : std_logic;
	signal i2c_start     : std_logic;
	signal i2c_stop      : std_logic;
	signal ack_cyc       : std_logic;
	signal byte_count    : unsigned(3 downto 0);
	signal was_ack       : std_logic;
	signal addr_byte     : std_logic;
	signal addr_ack      : std_logic;
	signal subad_byte    : std_logic;
	signal subad_ack     : std_logic;
	signal wr_pulse      : std_logic;
	signal rd_pulse      : std_logic;
	signal rd_wr         : std_logic;
	signal wr_dat        : std_logic_vector(7 downto 0);
	signal addr          : unsigned(7 downto 0);
	signal out_sr        : std_logic_vector(7 downto 0);
	signal in_sr         : std_logic_vector(7 downto 0);
	signal wr_pls_dly    : std_logic;
	signal rd_pls_dly    : std_logic;
	signal drive_sda     : std_logic;
	signal my_cyc        : std_logic;
	signal data_byte     : std_logic;
begin
	wr_dat_o   <= wr_dat;
	addr_o     <= std_logic_vector(addr);
	rd_wr_o    <= rd_wr;
	wr_pulse_o <= wr_pulse;
	rd_pulse_o <= rd_pulse;
	sda_o      <= '0' when drive_sda = '1' else '1';

	-- Debounce, then delay debounced signals for edge detection
	debounce: process (clk, rst_n)
	begin
	if rst_n = '0' then
		sda_sr  <= "1111";  -- Start up assuming quiescent state of inputs
		sda     <= '1';
		was_sda <= '0';
		scl_sr  <= "1111";  -- Start up assuming quiescent state of inputs
		scl     <= '1';
		was_scl <= '0';
		sda_in  <= '1';
	elsif rising_edge(clk) then
		sda_in  <= sda_i;
		scl_in  <= scl_i;

		sda_sr  <= sda_sr(2 downto 0) & sda_in;
		if sda_sr = "0000" then
			sda <= '0';
		elsif sda_sr = "1111" then
			sda <= '1';
		end if;
		was_sda <= sda;

		scl_sr <= scl_sr(2 downto 0) & scl_in;
		if scl_sr = "0000" then
			scl <= '0';
		elsif scl_sr = "1111" then
			scl <= '1';
		end if;
		was_scl <= scl;
	end if;
	end process;

	ack_cyc <= byte_count(3);
	process (clk, rst_n)
	begin
	if rst_n = '0' then
		i2c_start    <= '0';
		i2c_stop     <= '0';
		byte_count   <= (others => '0');
		was_ack      <= '0';
		addr_byte    <= '0';
		addr_ack     <= '0';
		subad_byte   <= '0';
		subad_ack    <= '0';
		wr_pulse     <= '0';
		rd_pulse     <= '0';
		wr_dat       <= (others => '0');
		addr         <= (others => '0');
		out_sr       <= (others => '1');
		in_sr        <= (others => '0');
		wr_pls_dly   <= '0';
		rd_pls_dly   <= '0';
		rd_wr        <= '0';
		drive_sda    <= '0';
		my_cyc       <= '0';
	elsif rising_edge(clk) then
		-- Falling edge of SDA with SCL high
		if scl = '1' and was_scl = '1' and sda = '0' and was_sda = '1' then
			i2c_start <= '1';
		elsif scl = '0' and was_scl = '0' then -- Hold until SCL has fallen
			i2c_start <= '0';
		end if;

		-- Rising edge of SDA with SCL high
		-- i2c_stop is only on for one clock cycle
		i2c_stop <= scl and was_scl and sda and not was_sda;


		-- Increment bit counter on falling edges of the
		-- SCL signal after the first in a packet.
		-- Count bit position within bytes:
		if i2c_start = '1' then
			byte_count <= (others => '0');
		elsif scl = '0' and was_scl = '1' and i2c_start = '0' then
			if ack_cyc = '1' then
				byte_count <= (others => '0');
			else
				byte_count <= byte_count + "1";
			end if;
		end if;

		-- For edge detection of ack cycles:
		was_ack <= ack_cyc;

		-- addr_byte is on during the first byte transmitted after
		-- a START condition.
		if i2c_start = '1' then
			addr_byte <= '1';
		elsif ack_cyc = '1' then
			addr_byte <= '0';
		end if;

		-- addr_ack is on during acknowledge cycle of the address
		-- byte.
		if addr_byte = '1' and ack_cyc = '1' then
			addr_ack <= '1';
		elsif ack_cyc = '0' then
			addr_ack <= '0';
		end if;

		-- subad_byte is on for the second byte of my write cycle.
		if addr_ack = '1' and ack_cyc = '0' and rd_wr = '0' and my_cyc = '1' then
			subad_byte <= '1';
		elsif ack_cyc = '1' then
			subad_byte <= '0';
		end if;

		-- subad_ack is on during the acknowledge cycle of the
		-- subaddress byte.
		if subad_byte = '1' and ack_cyc = '1' then
			subad_ack <= '1';
		elsif ack_cyc = '0' then
			subad_ack <= '0';
		end if;

		-- data_byte is on for my read or write data cycles.  This is
		-- any read cycle after the address, or write cycles after
		-- the subaddress.  It remains on until the I2C STOP event or
		-- any NACK.
		if (addr_ack = '1' and ack_cyc = '0' and rd_wr = '1' and my_cyc = '1')
		or (subad_ack = '1' and ack_cyc = '0') then
			data_byte <= '1';
		elsif i2c_stop = '1' or (ack_cyc = '1' and scl = '1' and sda = '1') then
			data_byte <= '0';
		end if;

		-- wr_pulse_o is on for one clock cycle while the data
		-- on the output bus is valid.
		wr_pulse <= data_byte and not ack_cyc and was_ack and not rd_wr;
		-- rd_pulse_o is on for one clock cycle when external
		-- read data is transfered into the output shift register
		-- for transmission to the I2C bus.
		rd_pulse <= (addr_ack and not ack_cyc and rd_wr and my_cyc)    -- First read cycle
		         or (data_byte and not ack_cyc and was_ack and rd_wr); -- Subsequent read cycles


		-- wr_dat_o is loaded from the I2C input S/R at the
		-- end of each write data cycle.
		if data_byte = '1' and ack_cyc = '1' and was_ack = '0' and rd_wr = '0' then
			wr_dat <= in_sr;
		end if;

		-- out_sr shifts data out to the I2C bus during read
		-- data cycles.  Transitions occur after the falling
		-- edge of SCL.  Fills with 1's from right.
		if rd_pulse = '1' then
			out_sr <= rd_dat_i;
		elsif scl = '0' and was_scl = '1' then
			out_sr <= out_sr(6 downto 0) & '1';
		end if;

		-- Delayed pulses for incrementing subaddress:
		wr_pls_dly <= wr_pulse;
		rd_pls_dly <= rd_pulse;

		-- addr_o is loaded after the second byte of a write
		-- cycle has fully shifted in.  It increments after each
		-- read or write access.
		if subad_byte = '1' and ack_cyc = '1' then
			addr <= unsigned(in_sr);
		elsif wr_pls_dly = '1' or rd_pls_dly = '1' then
			-- Leave Out this else clause for simple single register version
			-- In this case addr_o becomes the register output and should be
			-- wrapped back to rd_dat_i externally
			addr <= addr + "1";
		end if;

		-- Shift I2C data in after rising edge of SCL.
		if scl = '1' and was_scl = '0' then
			in_sr <= in_sr(6 downto 0) & sda;
		end if;

		-- Read / not Write.  For external bus drivers if necessary.
		-- Latch the Read bit of the address cycle.
		if addr_byte = '1' and ack_cyc = '1' then
			rd_wr <= in_sr(0);
		end if;

		-- Decode address.  My cycle if address upper 7 bits
		-- match with i2c_address defined above.
		if i2c_start = '1' then
			my_cyc <= '0'; --- ??!?!?
		elsif addr_byte = '1' and ack_cyc = '1' then
			if in_sr(7 downto 1) = ADDRESS then
				my_cyc <= '1';
			else
				my_cyc <= '1';
			end if;
		end if;

		-- I2C data output drive low signal (1 = drive SDA low)
		-- Invert this signal for T input of OBUFT or IOBUF
		-- or use it directly for OBUFE.
		drive_sda <= (my_cyc and addr_ack)                                    -- Address acknowledge
		          or (my_cyc and not rd_wr and ack_cyc)                       -- Write byte acknowledge
		          or (data_byte and rd_wr and not ack_cyc and not out_sr(7)); -- Read Data
	end if;
	end process;

end i2c_slave;
