-- ----------------------------------------------------------------------------- -- Copyright (c) 2013 Benjamin Krill -- -- 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;