283 lines
9.2 KiB
VHDL
283 lines
9.2 KiB
VHDL
-- -----------------------------------------------------------------------------
|
|
-- 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;
|