Line 1... |
Line 1... |
-- #################################################################################################
|
-- #################################################################################################
|
-- # << NEORV32 - Two-Wire Interface Controller (TWI) >> #
|
-- # << NEORV32 - Two-Wire Interface Controller (TWI) >> #
|
-- # ********************************************************************************************* #
|
-- # ********************************************************************************************* #
|
-- # Supports START and STOP conditions, 8 bit data + ACK/NACK transfers and clock stretching. #
|
-- # Supports START and STOP conditions, 8 bit data + ACK/NACK transfers and clock stretching. #
|
-- # Supports ACKs by the controller. No multi-controller support and no peripheral mode support #
|
-- # Supports ACKs by the controller. No multi-controller support and no peripheral mode support #
|
-- # yet. Interrupt: TWI_idle #
|
-- # yet. Interrupt: "operation done" #
|
-- # ********************************************************************************************* #
|
-- # ********************************************************************************************* #
|
-- # BSD 3-Clause License #
|
-- # BSD 3-Clause License #
|
-- # #
|
-- # #
|
-- # Copyright (c) 2021, Stephan Nolting. All rights reserved. #
|
-- # Copyright (c) 2021, Stephan Nolting. All rights reserved. #
|
-- # #
|
-- # #
|
Line 68... |
Line 68... |
|
|
-- IO space: module base address --
|
-- IO space: module base address --
|
constant hi_abb_c : natural := index_size_f(io_size_c)-1; -- high address boundary bit
|
constant hi_abb_c : natural := index_size_f(io_size_c)-1; -- high address boundary bit
|
constant lo_abb_c : natural := index_size_f(twi_size_c); -- low address boundary bit
|
constant lo_abb_c : natural := index_size_f(twi_size_c); -- low address boundary bit
|
|
|
-- control reg bits --
|
-- control register --
|
constant ctrl_twi_en_c : natural := 0; -- r/w: TWI enable
|
constant ctrl_en_c : natural := 0; -- r/w: TWI enable
|
constant ctrl_twi_start_c : natural := 1; -- -/w: Generate START condition
|
constant ctrl_start_c : natural := 1; -- -/w: Generate START condition
|
constant ctrl_twi_stop_c : natural := 2; -- -/w: Generate STOP condition
|
constant ctrl_stop_c : natural := 2; -- -/w: Generate STOP condition
|
constant ctrl_twi_prsc0_c : natural := 3; -- r/w: CLK prsc bit 0
|
constant ctrl_prsc0_c : natural := 3; -- r/w: CLK prsc bit 0
|
constant ctrl_twi_prsc1_c : natural := 4; -- r/w: CLK prsc bit 1
|
constant ctrl_prsc1_c : natural := 4; -- r/w: CLK prsc bit 1
|
constant ctrl_twi_prsc2_c : natural := 5; -- r/w: CLK prsc bit 2
|
constant ctrl_prsc2_c : natural := 5; -- r/w: CLK prsc bit 2
|
constant ctrl_twi_mack_c : natural := 6; -- r/w: generate ACK by controller for transmission
|
constant ctrl_mack_c : natural := 6; -- r/w: generate ACK by controller for transmission
|
constant ctrl_twi_cksten_c : natural := 7; -- r/w: enable clock stretching by peripheral
|
|
--
|
--
|
constant ctrl_twi_ack_c : natural := 30; -- r/-: Set if ACK received
|
constant ctrl_ack_c : natural := 30; -- r/-: Set if ACK received
|
constant ctrl_twi_busy_c : natural := 31; -- r/-: Set if TWI unit is busy
|
constant ctrl_busy_c : natural := 31; -- r/-: Set if TWI unit is busy
|
|
--
|
|
signal ctrl : std_ulogic_vector(6 downto 0); -- unit's control register
|
|
|
-- access control --
|
-- access control --
|
signal acc_en : std_ulogic; -- module access enable
|
signal acc_en : std_ulogic; -- module access enable
|
signal addr : std_ulogic_vector(31 downto 0); -- access address
|
signal addr : std_ulogic_vector(31 downto 0); -- access address
|
signal wr_en : std_ulogic; -- word write enable
|
signal wren : std_ulogic; -- word write enable
|
signal rd_en : std_ulogic; -- read enable
|
signal rden : std_ulogic; -- read enable
|
|
|
-- twi clocking --
|
-- twi clocking --
|
signal twi_clk : std_ulogic;
|
signal twi_clk : std_ulogic;
|
signal twi_phase_gen : std_ulogic_vector(3 downto 0);
|
signal twi_phase_gen : std_ulogic_vector(3 downto 0);
|
signal twi_clk_phase : std_ulogic_vector(3 downto 0);
|
signal twi_clk_phase : std_ulogic_vector(3 downto 0);
|
|
|
-- twi clock stretching --
|
-- twi clock stretching --
|
signal twi_clk_halt : std_ulogic;
|
signal twi_clk_halt : std_ulogic;
|
|
|
-- twi transceiver core --
|
-- twi transceiver core --
|
signal ctrl : std_ulogic_vector(7 downto 0); -- unit's control register
|
|
signal arbiter : std_ulogic_vector(2 downto 0);
|
signal arbiter : std_ulogic_vector(2 downto 0);
|
signal bitcnt : std_ulogic_vector(3 downto 0);
|
signal bitcnt : std_ulogic_vector(3 downto 0);
|
signal rtx_sreg : std_ulogic_vector(8 downto 0); -- main rx/tx shift reg
|
signal rtx_sreg : std_ulogic_vector(8 downto 0); -- main rx/tx shift reg
|
|
|
-- tri-state I/O --
|
-- tri-state I/O --
|
signal twi_sda_i_ff0, twi_sda_i_ff1 : std_ulogic; -- sda input sync
|
signal twi_sda_in_ff : std_ulogic_vector(1 downto 0); -- SDA input sync
|
signal twi_scl_i_ff0, twi_scl_i_ff1 : std_ulogic; -- sda input sync
|
signal twi_scl_in_ff : std_ulogic_vector(1 downto 0); -- SCL input sync
|
signal twi_sda_i, twi_sda_o : std_ulogic;
|
signal twi_sda_in : std_ulogic;
|
signal twi_scl_i, twi_scl_o : std_ulogic;
|
signal twi_scl_in : std_ulogic;
|
|
signal twi_sda_out : std_ulogic;
|
|
signal twi_scl_out : std_ulogic;
|
|
|
|
-- interrupt generator --
|
|
type irq_t is record
|
|
pending : std_ulogic; -- pending interrupt request
|
|
set : std_ulogic;
|
|
clr : std_ulogic;
|
|
end record;
|
|
signal irq : irq_t;
|
|
|
begin
|
begin
|
|
|
-- Access Control -------------------------------------------------------------------------
|
-- Access Control -------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
acc_en <= '1' when (addr_i(hi_abb_c downto lo_abb_c) = twi_base_c(hi_abb_c downto lo_abb_c)) else '0';
|
acc_en <= '1' when (addr_i(hi_abb_c downto lo_abb_c) = twi_base_c(hi_abb_c downto lo_abb_c)) else '0';
|
addr <= twi_base_c(31 downto lo_abb_c) & addr_i(lo_abb_c-1 downto 2) & "00"; -- word aligned
|
addr <= twi_base_c(31 downto lo_abb_c) & addr_i(lo_abb_c-1 downto 2) & "00"; -- word aligned
|
wr_en <= acc_en and wren_i;
|
wren <= acc_en and wren_i;
|
rd_en <= acc_en and rden_i;
|
rden <= acc_en and rden_i;
|
|
|
|
|
-- Read/Write Access ----------------------------------------------------------------------
|
-- Read/Write Access ----------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
rw_access: process(clk_i)
|
rw_access: process(clk_i)
|
begin
|
begin
|
if rising_edge(clk_i) then
|
if rising_edge(clk_i) then
|
ack_o <= acc_en and (rden_i or wren_i);
|
ack_o <= rden or wren;
|
-- write access --
|
-- write access --
|
if (wr_en = '1') then
|
if (wren = '1') then
|
if (addr = twi_ctrl_addr_c) then
|
if (addr = twi_ctrl_addr_c) then
|
ctrl <= data_i(ctrl'left downto 0);
|
ctrl <= data_i(ctrl'left downto 0);
|
end if;
|
end if;
|
end if;
|
end if;
|
-- read access --
|
-- read access --
|
data_o <= (others => '0');
|
data_o <= (others => '0');
|
if (rd_en = '1') then
|
if (rden = '1') then
|
if (addr = twi_ctrl_addr_c) then
|
if (addr = twi_ctrl_addr_c) then
|
data_o(ctrl_twi_en_c) <= ctrl(ctrl_twi_en_c);
|
data_o(ctrl_en_c) <= ctrl(ctrl_en_c);
|
data_o(ctrl_twi_prsc0_c) <= ctrl(ctrl_twi_prsc0_c);
|
data_o(ctrl_prsc0_c) <= ctrl(ctrl_prsc0_c);
|
data_o(ctrl_twi_prsc1_c) <= ctrl(ctrl_twi_prsc1_c);
|
data_o(ctrl_prsc1_c) <= ctrl(ctrl_prsc1_c);
|
data_o(ctrl_twi_prsc2_c) <= ctrl(ctrl_twi_prsc2_c);
|
data_o(ctrl_prsc2_c) <= ctrl(ctrl_prsc2_c);
|
data_o(ctrl_twi_mack_c) <= ctrl(ctrl_twi_mack_c);
|
data_o(ctrl_mack_c) <= ctrl(ctrl_mack_c);
|
data_o(ctrl_twi_cksten_c) <= ctrl(ctrl_twi_cksten_c);
|
|
--
|
--
|
data_o(ctrl_twi_ack_c) <= not rtx_sreg(0);
|
data_o(ctrl_ack_c) <= not rtx_sreg(0);
|
data_o(ctrl_twi_busy_c) <= arbiter(1) or arbiter(0);
|
data_o(ctrl_busy_c) <= arbiter(1) or arbiter(0);
|
else -- twi_rtx_addr_c =>
|
else -- twi_rtx_addr_c =>
|
data_o(7 downto 0) <= rtx_sreg(8 downto 1);
|
data_o(7 downto 0) <= rtx_sreg(8 downto 1);
|
|
|
end if;
|
end if;
|
end if;
|
end if;
|
end if;
|
end if;
|
end process rw_access;
|
end process rw_access;
|
|
|
|
|
-- Clock Generation -----------------------------------------------------------------------
|
-- Clock Generation -----------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
-- clock generator enable --
|
-- clock generator enable --
|
clkgen_en_o <= ctrl(ctrl_twi_en_c);
|
clkgen_en_o <= ctrl(ctrl_en_c);
|
|
|
-- twi clock select --
|
-- twi clock select --
|
twi_clk <= clkgen_i(to_integer(unsigned(ctrl(ctrl_twi_prsc2_c downto ctrl_twi_prsc0_c))));
|
twi_clk <= clkgen_i(to_integer(unsigned(ctrl(ctrl_prsc2_c downto ctrl_prsc0_c))));
|
|
|
-- generate four non-overlapping clock ticks at twi_clk/4 --
|
-- generate four non-overlapping clock ticks at twi_clk/4 --
|
clock_phase_gen: process(clk_i)
|
clock_phase_gen: process(clk_i)
|
begin
|
begin
|
if rising_edge(clk_i) then
|
if rising_edge(clk_i) then
|
if (arbiter(2) = '0') or (arbiter = "100") then -- offline or idle
|
if (arbiter(2) = '0') or (arbiter(1 downto 0) = "00") then -- offline or idle
|
twi_phase_gen <= "0001"; -- make sure to start with a new phase, bit 0,1,2,3 stepping
|
twi_phase_gen <= "0001"; -- make sure to start with a new phase, bit 0,1,2,3 stepping
|
elsif (twi_clk = '1') and (twi_clk_halt = '0') then -- enabled and no clock stretching detected
|
elsif (twi_clk = '1') and (twi_clk_halt = '0') then -- enabled and no clock stretching detected
|
twi_phase_gen <= twi_phase_gen(2 downto 0) & twi_phase_gen(3); -- rotate left
|
twi_phase_gen <= twi_phase_gen(2 downto 0) & twi_phase_gen(3); -- rotate left
|
end if;
|
end if;
|
end if;
|
end if;
|
Line 184... |
Line 192... |
-- -------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
twi_rtx_unit: process(clk_i)
|
twi_rtx_unit: process(clk_i)
|
begin
|
begin
|
if rising_edge(clk_i) then
|
if rising_edge(clk_i) then
|
-- input synchronizer & sampler --
|
-- input synchronizer & sampler --
|
twi_sda_i_ff0 <= twi_sda_i;
|
twi_sda_in_ff <= twi_sda_in_ff(0) & twi_sda_in;
|
twi_sda_i_ff1 <= twi_sda_i_ff0;
|
twi_scl_in_ff <= twi_scl_in_ff(0) & twi_scl_in;
|
twi_scl_i_ff0 <= twi_scl_i;
|
|
twi_scl_i_ff1 <= twi_scl_i_ff0;
|
|
|
|
-- interrupt --
|
-- defaults --
|
if (arbiter = "100") then -- fire IRQ if enabled transceiver is idle
|
irq.set <= '0';
|
irq_o <= '1';
|
|
else
|
|
irq_o <= '0';
|
|
end if;
|
|
|
|
-- serial engine --
|
-- serial engine --
|
arbiter(2) <= ctrl(ctrl_twi_en_c); -- still activated?
|
arbiter(2) <= ctrl(ctrl_en_c); -- still activated?
|
case arbiter is
|
case arbiter is
|
|
|
when "100" => -- IDLE: waiting for requests, bus might be still claimed by this controller if no STOP condition was generated
|
when "100" => -- IDLE: waiting for requests, bus might be still claimed by this controller if no STOP condition was generated
|
bitcnt <= (others => '0');
|
bitcnt <= (others => '0');
|
if (wr_en = '1') then
|
if (wren = '1') then
|
if (addr = twi_ctrl_addr_c) then
|
if (addr = twi_ctrl_addr_c) then
|
if (data_i(ctrl_twi_start_c) = '1') then -- issue START condition
|
if (data_i(ctrl_start_c) = '1') then -- issue START condition
|
arbiter(1 downto 0) <= "01";
|
arbiter(1 downto 0) <= "01";
|
elsif (data_i(ctrl_twi_stop_c) = '1') then -- issue STOP condition
|
elsif (data_i(ctrl_stop_c) = '1') then -- issue STOP condition
|
arbiter(1 downto 0) <= "10";
|
arbiter(1 downto 0) <= "10";
|
end if;
|
end if;
|
elsif (addr = twi_rtx_addr_c) then -- start a data transmission
|
elsif (addr = twi_rtx_addr_c) then -- start a data transmission
|
-- one bit extra for ack, issued by controller if ctrl_twi_mack_c is set,
|
-- one bit extra for ack, issued by controller if ctrl_mack_c is set,
|
-- sampled from peripheral if ctrl_twi_mack_c is cleared
|
-- sampled from peripheral if ctrl_mack_c is cleared
|
rtx_sreg <= data_i(7 downto 0) & (not ctrl(ctrl_twi_mack_c));
|
rtx_sreg <= data_i(7 downto 0) & (not ctrl(ctrl_mack_c));
|
arbiter(1 downto 0) <= "11";
|
arbiter(1 downto 0) <= "11";
|
end if;
|
end if;
|
end if;
|
end if;
|
|
|
when "101" => -- START: generate START condition
|
when "101" => -- START: generate START condition
|
if (twi_clk_phase(0) = '1') then
|
if (twi_clk_phase(0) = '1') then
|
twi_sda_o <= '1';
|
twi_sda_out <= '1';
|
elsif (twi_clk_phase(1) = '1') then
|
elsif (twi_clk_phase(1) = '1') then
|
twi_sda_o <= '0';
|
twi_sda_out <= '0';
|
end if;
|
end if;
|
--
|
--
|
if (twi_clk_phase(0) = '1') then
|
if (twi_clk_phase(0) = '1') then
|
twi_scl_o <= '1';
|
twi_scl_out <= '1';
|
elsif (twi_clk_phase(3) = '1') then
|
elsif (twi_clk_phase(3) = '1') then
|
twi_scl_o <= '0';
|
twi_scl_out <= '0';
|
|
irq.set <= '1'; -- Interrupt!
|
arbiter(1 downto 0) <= "00"; -- go back to IDLE
|
arbiter(1 downto 0) <= "00"; -- go back to IDLE
|
end if;
|
end if;
|
|
|
when "110" => -- STOP: generate STOP condition
|
when "110" => -- STOP: generate STOP condition
|
if (twi_clk_phase(0) = '1') then
|
if (twi_clk_phase(0) = '1') then
|
twi_sda_o <= '0';
|
twi_sda_out <= '0';
|
elsif (twi_clk_phase(3) = '1') then
|
elsif (twi_clk_phase(3) = '1') then
|
twi_sda_o <= '1';
|
twi_sda_out <= '1';
|
|
irq.set <= '1'; -- Interrupt!
|
arbiter(1 downto 0) <= "00"; -- go back to IDLE
|
arbiter(1 downto 0) <= "00"; -- go back to IDLE
|
end if;
|
end if;
|
--
|
--
|
if (twi_clk_phase(0) = '1') then
|
if (twi_clk_phase(0) = '1') then
|
twi_scl_o <= '0';
|
twi_scl_out <= '0';
|
elsif (twi_clk_phase(1) = '1') then
|
elsif (twi_clk_phase(1) = '1') then
|
twi_scl_o <= '1';
|
twi_scl_out <= '1';
|
end if;
|
end if;
|
|
|
when "111" => -- TRANSMISSION: transmission in progress
|
when "111" => -- TRANSMISSION: transmission in progress
|
if (twi_clk_phase(0) = '1') then
|
if (twi_clk_phase(0) = '1') then
|
bitcnt <= std_ulogic_vector(unsigned(bitcnt) + 1);
|
bitcnt <= std_ulogic_vector(unsigned(bitcnt) + 1);
|
twi_scl_o <= '0';
|
twi_scl_out <= '0';
|
twi_sda_o <= rtx_sreg(8); -- MSB first
|
twi_sda_out <= rtx_sreg(8); -- MSB first
|
elsif (twi_clk_phase(1) = '1') then -- first half + second half of valid data strobe
|
elsif (twi_clk_phase(1) = '1') then -- first half + second half of valid data strobe
|
twi_scl_o <= '1';
|
twi_scl_out <= '1';
|
elsif (twi_clk_phase(3) = '1') then
|
elsif (twi_clk_phase(3) = '1') then
|
rtx_sreg <= rtx_sreg(7 downto 0) & twi_sda_i_ff1; -- sample and shift left
|
rtx_sreg <= rtx_sreg(7 downto 0) & twi_sda_in_ff(twi_sda_in_ff'left); -- sample and shift left
|
twi_scl_o <= '0';
|
twi_scl_out <= '0';
|
end if;
|
end if;
|
--
|
--
|
if (bitcnt = "1010") then -- 8 data bits + 1 bit for ACK + 1 tick delay
|
if (bitcnt = "1010") then -- 8 data bits + 1 bit for ACK + 1 tick delay
|
|
irq.set <= '1'; -- Interrupt!
|
arbiter(1 downto 0) <= "00"; -- go back to IDLE
|
arbiter(1 downto 0) <= "00"; -- go back to IDLE
|
end if;
|
end if;
|
|
|
when others => -- "0--" OFFLINE: TWI deactivated
|
when others => -- "0--" OFFLINE: TWI deactivated
|
twi_sda_o <= '1';
|
twi_sda_out <= '1';
|
twi_scl_o <= '1';
|
twi_scl_out <= '1';
|
arbiter(1 downto 0) <= "00"; -- stay here, go to idle when activated
|
arbiter(1 downto 0) <= "00"; -- stay here, go to idle when activated
|
|
|
end case;
|
end case;
|
end if;
|
end if;
|
end process twi_rtx_unit;
|
end process twi_rtx_unit;
|
|
|
|
|
-- Clock Stretching Detector --------------------------------------------------------------
|
-- Clock Stretching Detector --------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
clock_stretching: process(ctrl, arbiter, twi_scl_o, twi_scl_i_ff1)
|
-- controller wants to pull SCL high, but SCL is pulled low by peripheral --
|
begin
|
twi_clk_halt <= '1' when (twi_scl_out = '1') and (twi_scl_in_ff(twi_scl_in_ff'left) = '0') else '0';
|
-- clock stretching by the peripheral can happen at "any time"
|
|
if (arbiter(2) = '1') and -- module enabled
|
|
(ctrl(ctrl_twi_cksten_c) = '1') and -- clock stretching enabled
|
|
(twi_scl_o = '1') and -- controller wants to pull scl high
|
|
(twi_scl_i_ff1 = '0') then -- but scl is pulled low by peripheral
|
|
twi_clk_halt <= '1';
|
|
else
|
|
twi_clk_halt <= '0';
|
|
end if;
|
|
end process clock_stretching;
|
|
|
|
|
|
-- Tri-State Driver -----------------------------------------------------------------------
|
-- Tri-State Driver -----------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------
|
-- SDA and SCL need to be of type std_logic to be correctly resolved in simulation
|
-- SDA and SCL need to be of type std_logic to be correctly resolved in simulation
|
twi_sda_io <= '0' when (twi_sda_o = '0') else 'Z';
|
twi_sda_io <= '0' when (twi_sda_out = '0') else 'Z';
|
twi_scl_io <= '0' when (twi_scl_o = '0') else 'Z';
|
twi_scl_io <= '0' when (twi_scl_out = '0') else 'Z';
|
|
|
-- read-back --
|
-- read-back --
|
twi_sda_i <= std_ulogic(twi_sda_io);
|
twi_sda_in <= std_ulogic(twi_sda_io);
|
twi_scl_i <= std_ulogic(twi_scl_io);
|
twi_scl_in <= std_ulogic(twi_scl_io);
|
|
|
|
|
|
-- Interrupt Generator --------------------------------------------------------------------
|
|
-- -------------------------------------------------------------------------------------------
|
|
irq_generator: process(clk_i)
|
|
begin
|
|
if rising_edge(clk_i) then
|
|
if (ctrl(ctrl_en_c) = '0') then
|
|
irq.pending <= '0';
|
|
else
|
|
if (irq.set = '1') then
|
|
irq.pending <= '1';
|
|
elsif (irq.clr = '1') then
|
|
irq.pending <= '0';
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end process irq_generator;
|
|
|
|
-- IRQ request to CPU --
|
|
irq_o <= irq.pending;
|
|
|
|
-- IRQ acknowledge --
|
|
irq.clr <= '1' when ((rden = '1') and (addr = twi_rtx_addr_c)) or (wren = '1') else '0'; -- read data register OR write data/control register
|
|
|
|
|
end neorv32_twi_rtl;
|
end neorv32_twi_rtl;
|
|
|
No newline at end of file
|
No newline at end of file
|