Revision 980
Added by markw over 5 years ago
common/components/I2C_regs.vhd | ||
---|---|---|
---------------------------------------------------------------------------
|
||
-- (c) 2020 mark watson
|
||
-- I am happy for anyone to use this for non-commercial use.
|
||
-- If my vhdl files are used commercially or otherwise sold,
|
||
-- please contact me for explicit permission at scrameta (gmail).
|
||
-- This applies for source and binary form and derived works.
|
||
---------------------------------------------------------------------------
|
||
|
||
LIBRARY ieee;
|
||
USE ieee.std_logic_1164.all;
|
||
use ieee.numeric_std.all;
|
||
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
|
||
use IEEE.STD_LOGIC_MISC.all;
|
||
|
||
ENTITY I2C_regs IS
|
||
generic (
|
||
SLAVE_ADDR : std_logic_vector(6 downto 0);
|
||
regs : integer := 1; -- up to 16
|
||
bits : integer := 1
|
||
);
|
||
port (
|
||
scl : inout std_logic;
|
||
sda : inout std_logic;
|
||
clk : in std_logic;
|
||
rst : in std_logic;
|
||
|
||
-- User interface
|
||
reg : out std_logic_vector((regs*bits)-1 downto 0)
|
||
);
|
||
END I2C_regs;
|
||
|
||
ARCHITECTURE vhdl OF I2C_regs IS
|
||
signal i2c_write : std_logic;
|
||
signal i2c_read : std_logic;
|
||
signal i2c_write_data : std_logic_vector(7 downto 0);
|
||
signal i2c_read_data : std_logic_vector(7 downto 0);
|
||
|
||
signal i2c_addr_next : std_logic_vector(3 downto 0);
|
||
signal i2c_addr_reg : std_logic_vector(3 downto 0);
|
||
|
||
signal i2c_state_next : std_logic_vector(2 downto 0);
|
||
signal i2c_state_reg : std_logic_vector(2 downto 0);
|
||
constant I2C_INIT : std_logic_vector(2 downto 0) := "000";
|
||
constant I2C_READ1 : std_logic_vector(2 downto 0) := "001";
|
||
constant I2C_READ2 : std_logic_vector(2 downto 0) := "010";
|
||
constant I2C_WRITE1 : std_logic_vector(2 downto 0) := "011";
|
||
constant I2C_WRITE2 : std_logic_vector(2 downto 0) := "100";
|
||
|
||
signal reg_next : std_logic_vector((regs*bits)-1 downto 0);
|
||
signal reg_reg : std_logic_vector((regs*bits)-1 downto 0);
|
||
|
||
function MIN(LEFT, RIGHT: INTEGER) return INTEGER is
|
||
begin
|
||
if LEFT < RIGHT then return LEFT;
|
||
else return RIGHT;
|
||
end if;
|
||
end;
|
||
|
||
function IX(r,c : natural) return natural is
|
||
begin
|
||
return (r*bits + c);
|
||
end;
|
||
BEGIN
|
||
|
||
process(clk,rst)
|
||
begin
|
||
if (rst='1') then
|
||
reg_reg <= (others=>'0');
|
||
|
||
-- i2c
|
||
i2c_state_reg <= I2C_INIT;
|
||
i2c_addr_reg <= (others=>'0');
|
||
elsif (clk'event and clk='1') then
|
||
reg_reg <= reg_next;
|
||
|
||
-- i2c
|
||
i2c_state_reg <= i2c_state_next;
|
||
i2c_addr_reg <= i2c_addr_next;
|
||
end if;
|
||
end process;
|
||
|
||
i2cslave : entity work.I2C_slave
|
||
generic map (
|
||
SLAVE_ADDR => SLAVE_ADDR
|
||
)
|
||
port map (
|
||
scl => scl,
|
||
sda => sda,
|
||
clk => clk,
|
||
rst => rst,
|
||
|
||
read_req => i2c_read,
|
||
data_to_master => i2c_read_data,
|
||
data_valid => i2c_write,
|
||
data_from_master => i2c_write_data
|
||
);
|
||
|
||
process(
|
||
reg_reg,
|
||
i2c_addr_reg,
|
||
i2c_state_reg,
|
||
i2c_read,
|
||
i2c_write,
|
||
i2c_write_data
|
||
)
|
||
variable low_max : integer;
|
||
variable i2c_addr_int : integer;
|
||
begin
|
||
low_max := min(7,bits-1);
|
||
i2c_addr_int := to_integer(unsigned(i2c_addr_reg));
|
||
|
||
reg_next <= reg_reg;
|
||
|
||
i2c_addr_next <= i2c_addr_reg;
|
||
i2c_state_next <= i2c_state_reg;
|
||
|
||
i2c_read_data <= (others=>'0');
|
||
|
||
case(i2c_state_reg) is
|
||
when I2C_INIT =>
|
||
if (i2c_write='1' and i2c_write_data(7 downto 5)="111") then -- F= write, E= read, bottom 4 bits = reg
|
||
i2c_addr_next <= i2c_write_data(3 downto 0);
|
||
if (i2c_write_data(4)='1') then
|
||
i2c_state_next <= I2C_WRITE1;
|
||
else
|
||
i2c_state_next <= I2C_READ1;
|
||
end if;
|
||
end if;
|
||
when I2C_WRITE1 =>
|
||
if (i2c_write='1') then
|
||
for i in 0 to regs-1 loop
|
||
if (i2c_addr_int=i) then
|
||
reg_next(IX(i,low_max) downto IX(i,0)) <= i2c_write_data(low_max downto 0);
|
||
end if;
|
||
end loop;
|
||
i2c_state_next <= I2C_WRITE2;
|
||
end if;
|
||
when I2C_WRITE2 =>
|
||
if (i2c_write='1') then
|
||
for i in 0 to regs-1 loop
|
||
if (i2c_addr_int=i) then
|
||
reg_next(ix(i,bits-1) downto ix(i, 8)) <= i2c_write_data(bits-9 downto 0);
|
||
end if;
|
||
end loop;
|
||
i2c_state_next <= I2C_INIT;
|
||
end if;
|
||
when I2C_READ1 =>
|
||
for i in 0 to regs-1 loop
|
||
if (i2c_addr_int=i) then
|
||
i2c_read_data(low_max downto 0) <= reg_reg(ix(i,low_max) downto ix(i,0));
|
||
end if;
|
||
end loop;
|
||
if (i2c_read='1') then
|
||
i2c_state_next <= I2C_READ2;
|
||
end if;
|
||
when I2C_READ2 =>
|
||
for i in 0 to regs-1 loop
|
||
if (i2c_addr_int=i) then
|
||
i2c_read_data(bits-9 downto 0) <= reg_reg(ix(i,bits-1) downto ix(i,8));
|
||
end if;
|
||
end loop;
|
||
|
||
if (i2c_read='1') then
|
||
i2c_state_next <= I2C_INIT;
|
||
end if;
|
||
when others =>
|
||
i2c_state_next <= I2C_INIT;
|
||
end case;
|
||
end process;
|
||
|
||
reg <= reg_reg;
|
||
|
||
end vhdl;
|
||
|
||
|
common/components/I2C_slave.vhd | ||
---|---|---|
------------------------------------------------------------
|
||
-- File : I2C_slave.vhd
|
||
------------------------------------------------------------
|
||
-- Author : Peter Samarin <peter.samarin@gmail.com>
|
||
------------------------------------------------------------
|
||
-- Copyright (c) 2016 Peter Samarin
|
||
------------------------------------------------------------
|
||
library ieee;
|
||
use ieee.std_logic_1164.all;
|
||
use ieee.numeric_std.all;
|
||
------------------------------------------------------------
|
||
entity I2C_slave is
|
||
generic (
|
||
SLAVE_ADDR : std_logic_vector(6 downto 0));
|
||
port (
|
||
scl : inout std_logic;
|
||
sda : inout std_logic;
|
||
clk : in std_logic;
|
||
rst : in std_logic;
|
||
-- User interface
|
||
read_req : out std_logic;
|
||
data_to_master : in std_logic_vector(7 downto 0);
|
||
data_valid : out std_logic;
|
||
data_from_master : out std_logic_vector(7 downto 0));
|
||
end entity I2C_slave;
|
||
------------------------------------------------------------
|
||
architecture arch of I2C_slave is
|
||
-- this assumes that system's clock is much faster than SCL
|
||
constant DEBOUNCING_WAIT_CYCLES : integer := 4;
|
||
|
||
type state_t is (idle, get_address_and_cmd,
|
||
answer_ack_start, write,
|
||
read, read_ack_start,
|
||
read_ack_got_rising, read_stop);
|
||
-- I2C state management
|
||
signal state_reg : state_t := idle;
|
||
signal cmd_reg : std_logic := '0';
|
||
signal bits_processed_reg : integer range 0 to 8 := 0;
|
||
signal continue_reg : std_logic := '0';
|
||
|
||
signal scl_reg : std_logic := '1';
|
||
signal sda_reg : std_logic := '1';
|
||
signal scl_debounced : std_logic := '1';
|
||
signal sda_debounced : std_logic := '1';
|
||
|
||
|
||
-- Helpers to figure out next state
|
||
signal start_reg : std_logic := '0';
|
||
signal stop_reg : std_logic := '0';
|
||
signal scl_rising_reg : std_logic := '0';
|
||
signal scl_falling_reg : std_logic := '0';
|
||
|
||
-- Address and data received from master
|
||
signal addr_reg : std_logic_vector(6 downto 0) := (others => '0');
|
||
signal data_reg : std_logic_vector(6 downto 0) := (others => '0');
|
||
signal data_from_master_reg : std_logic_vector(7 downto 0) := (others => '0');
|
||
|
||
signal scl_prev_reg : std_logic := '1';
|
||
-- Slave writes on scl
|
||
signal scl_wen_reg : std_logic := '0';
|
||
signal scl_o_reg : std_logic := '0';
|
||
signal sda_prev_reg : std_logic := '1';
|
||
-- Slave writes on sda
|
||
signal sda_wen_reg : std_logic := '0';
|
||
signal sda_o_reg : std_logic := '0';
|
||
|
||
-- User interface
|
||
signal data_valid_reg : std_logic := '0';
|
||
signal read_req_reg : std_logic := '0';
|
||
signal data_to_master_reg : std_logic_vector(7 downto 0) := (others => '0');
|
||
begin
|
||
|
||
-- debounce SCL and SDA
|
||
-- SCL_debounce : entity work.debounce
|
||
-- generic map (
|
||
-- WAIT_CYCLES => DEBOUNCING_WAIT_CYCLES)
|
||
-- port map (
|
||
-- clk => clk,
|
||
-- signal_in => scl_reg,
|
||
-- signal_out => scl_debounced);
|
||
|
||
scl_debounced <= '1' when scl_reg='H' else scl_reg;
|
||
|
||
-- -- it might not make sense to debounce SDA, since master
|
||
-- -- and slave can both write to it...
|
||
-- SDA_debounce : entity work.debounce
|
||
-- generic map (
|
||
-- WAIT_CYCLES => DEBOUNCING_WAIT_CYCLES)
|
||
-- port map (
|
||
-- clk => clk,
|
||
-- signal_in => sda_reg,
|
||
-- signal_out => sda_debounced);
|
||
|
||
sda_debounced <= '1' when sda_reg='H' else sda_reg;
|
||
|
||
process (clk) is
|
||
begin
|
||
if rising_edge(clk) then
|
||
-- save SCL in registers that are used for debouncing
|
||
scl_reg <= scl;
|
||
sda_reg <= sda;
|
||
|
||
-- Delay debounced SCL and SDA by 1 clock cycle
|
||
scl_prev_reg <= scl_debounced;
|
||
sda_prev_reg <= sda_debounced;
|
||
-- Detect rising and falling SCL
|
||
scl_rising_reg <= '0';
|
||
if scl_prev_reg = '0' and scl_debounced = '1' then
|
||
scl_rising_reg <= '1';
|
||
end if;
|
||
scl_falling_reg <= '0';
|
||
if scl_prev_reg = '1' and scl_debounced = '0' then
|
||
scl_falling_reg <= '1';
|
||
end if;
|
||
|
||
-- Detect I2C START condition
|
||
start_reg <= '0';
|
||
stop_reg <= '0';
|
||
if scl_debounced = '1' and scl_prev_reg = '1' and
|
||
sda_prev_reg = '1' and sda_debounced = '0' then
|
||
start_reg <= '1';
|
||
stop_reg <= '0';
|
||
end if;
|
||
|
||
-- Detect I2C STOP condition
|
||
if scl_prev_reg = '1' and scl_debounced = '1' and
|
||
sda_prev_reg = '0' and sda_debounced = '1' then
|
||
start_reg <= '0';
|
||
stop_reg <= '1';
|
||
end if;
|
||
|
||
end if;
|
||
end process;
|
||
|
||
----------------------------------------------------------
|
||
-- I2C state machine
|
||
----------------------------------------------------------
|
||
process (clk) is
|
||
begin
|
||
if rising_edge(clk) then
|
||
-- Default assignments
|
||
sda_o_reg <= '0';
|
||
sda_wen_reg <= '0';
|
||
-- User interface
|
||
data_valid_reg <= '0';
|
||
read_req_reg <= '0';
|
||
|
||
case state_reg is
|
||
|
||
when idle =>
|
||
if start_reg = '1' then
|
||
state_reg <= get_address_and_cmd;
|
||
bits_processed_reg <= 0;
|
||
end if;
|
||
|
||
when get_address_and_cmd =>
|
||
if scl_rising_reg = '1' then
|
||
if bits_processed_reg < 7 then
|
||
bits_processed_reg <= bits_processed_reg + 1;
|
||
addr_reg(6-bits_processed_reg) <= sda_debounced;
|
||
elsif bits_processed_reg = 7 then
|
||
bits_processed_reg <= bits_processed_reg + 1;
|
||
cmd_reg <= sda_debounced;
|
||
end if;
|
||
end if;
|
||
|
||
if bits_processed_reg = 8 and scl_falling_reg = '1' then
|
||
bits_processed_reg <= 0;
|
||
if addr_reg = SLAVE_ADDR then -- check req address
|
||
state_reg <= answer_ack_start;
|
||
if cmd_reg = '1' then -- issue read request
|
||
read_req_reg <= '1';
|
||
data_to_master_reg <= data_to_master;
|
||
end if;
|
||
else
|
||
assert false
|
||
report ("I2C: target/slave address mismatch (data is being sent to another slave).")
|
||
severity note;
|
||
state_reg <= idle;
|
||
end if;
|
||
end if;
|
||
|
||
----------------------------------------------------
|
||
-- I2C acknowledge to master
|
||
----------------------------------------------------
|
||
when answer_ack_start =>
|
||
sda_wen_reg <= '1';
|
||
sda_o_reg <= '0';
|
||
if scl_falling_reg = '1' then
|
||
if cmd_reg = '0' then
|
||
state_reg <= write;
|
||
else
|
||
state_reg <= read;
|
||
end if;
|
||
end if;
|
||
|
||
----------------------------------------------------
|
||
-- WRITE
|
||
----------------------------------------------------
|
||
when write =>
|
||
if scl_rising_reg = '1' then
|
||
bits_processed_reg <= bits_processed_reg + 1;
|
||
if bits_processed_reg < 7 then
|
||
data_reg(6-bits_processed_reg) <= sda_debounced;
|
||
else
|
||
data_from_master_reg <= data_reg & sda_debounced;
|
||
data_valid_reg <= '1';
|
||
end if;
|
||
end if;
|
||
|
||
if scl_falling_reg = '1' and bits_processed_reg = 8 then
|
||
state_reg <= answer_ack_start;
|
||
bits_processed_reg <= 0;
|
||
end if;
|
||
|
||
----------------------------------------------------
|
||
-- READ: send data to master
|
||
----------------------------------------------------
|
||
when read =>
|
||
sda_wen_reg <= '1';
|
||
sda_o_reg <= data_to_master_reg(7-bits_processed_reg);
|
||
if scl_falling_reg = '1' then
|
||
if bits_processed_reg < 7 then
|
||
bits_processed_reg <= bits_processed_reg + 1;
|
||
elsif bits_processed_reg = 7 then
|
||
state_reg <= read_ack_start;
|
||
bits_processed_reg <= 0;
|
||
end if;
|
||
end if;
|
||
|
||
----------------------------------------------------
|
||
-- I2C read master acknowledge
|
||
----------------------------------------------------
|
||
when read_ack_start =>
|
||
if scl_rising_reg = '1' then
|
||
state_reg <= read_ack_got_rising;
|
||
if sda_debounced = '1' then -- nack = stop read
|
||
continue_reg <= '0';
|
||
else -- ack = continue read
|
||
continue_reg <= '1';
|
||
read_req_reg <= '1'; -- request reg byte
|
||
data_to_master_reg <= data_to_master;
|
||
end if;
|
||
end if;
|
||
|
||
when read_ack_got_rising =>
|
||
if scl_falling_reg = '1' then
|
||
if continue_reg = '1' then
|
||
if cmd_reg = '0' then
|
||
state_reg <= write;
|
||
else
|
||
state_reg <= read;
|
||
end if;
|
||
else
|
||
state_reg <= read_stop;
|
||
end if;
|
||
end if;
|
||
|
||
-- Wait for START or STOP to get out of this state
|
||
when read_stop =>
|
||
null;
|
||
|
||
-- Wait for START or STOP to get out of this state
|
||
when others =>
|
||
assert false
|
||
report ("I2C: error: ended in an impossible state.")
|
||
severity error;
|
||
state_reg <= idle;
|
||
end case;
|
||
|
||
--------------------------------------------------------
|
||
-- Reset counter and state on start/stop
|
||
--------------------------------------------------------
|
||
if start_reg = '1' then
|
||
state_reg <= get_address_and_cmd;
|
||
bits_processed_reg <= 0;
|
||
end if;
|
||
|
||
if stop_reg = '1' then
|
||
state_reg <= idle;
|
||
bits_processed_reg <= 0;
|
||
end if;
|
||
|
||
if rst = '1' then
|
||
state_reg <= idle;
|
||
end if;
|
||
end if;
|
||
end process;
|
||
|
||
----------------------------------------------------------
|
||
-- I2C interface
|
||
----------------------------------------------------------
|
||
sda <= sda_o_reg when sda_wen_reg = '1' else
|
||
'Z';
|
||
scl <= scl_o_reg when scl_wen_reg = '1' else
|
||
'Z';
|
||
----------------------------------------------------------
|
||
-- User interface
|
||
----------------------------------------------------------
|
||
-- Master writes
|
||
data_valid <= data_valid_reg;
|
||
data_from_master <= data_from_master_reg;
|
||
-- Master reads
|
||
read_req <= read_req_reg;
|
||
end architecture arch;
|
Also available in: Unified diff
I2C registers, for the scaler config