hdet/fpga/src/i2c/slave/i2c_slave.vhd

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;