--------------------------------------------------------------------- ---- ---- ---- WISHBONE revB2 I2C Master Core; bit-controller ---- ---- ---- ---- ---- ---- Author: Richard Herveille ---- ---- richard@asics.ws ---- ---- www.asics.ws ---- ---- ---- ---- Downloaded from: http://www.opencores.org/projects/i2c/ ---- ---- ---- --------------------------------------------------------------------- ---- ---- ---- Copyright (C) 2000 Richard Herveille ---- ---- richard@asics.ws ---- ---- ---- ---- This source file may be used and distributed without ---- ---- restriction provided that this copyright statement is not ---- ---- removed from the file and that any derivative work contains ---- ---- the original copyright notice and the associated disclaimer.---- ---- ---- ---- THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY ---- ---- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ---- ---- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS ---- ---- FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR ---- ---- OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, ---- ---- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ---- ---- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE ---- ---- GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR ---- ---- BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ---- ---- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ---- ---- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ---- ---- OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ---- ---- POSSIBILITY OF SUCH DAMAGE. ---- ---- ---- --------------------------------------------------------------------- -- CVS Log -- -- $Id: i2c_master_bit_ctrl.vhd,v 1.17 2009-02-04 20:17:34 rherveille Exp $ -- -- $Date: 2009-02-04 20:17:34 $ -- $Revision: 1.17 $ -- $Author: rherveille $ -- $Locker: $ -- $State: Exp $ -- -- Change History: -- -- 03/04/2014 "keep the clock rolling" version -- -- $Log: not supported by cvs2svn $ -- Revision 1.16 2009/01/20 20:40:36 rherveille -- Fixed type iscl_oen instead of scl_oen -- -- Revision 1.15 2009/01/20 10:34:51 rherveille -- Added SCL clock synchronization logic -- Fixed slave_wait signal generation -- -- Revision 1.14 2006/10/11 12:10:13 rherveille -- Added missing semicolons ';' on endif -- -- Revision 1.13 2006/10/06 10:48:24 rherveille -- fixed short scl high pulse after clock stretch -- -- Revision 1.12 2004/05/07 11:53:31 rherveille -- Fixed previous fix :) Made a variable vs signal mistake. -- -- Revision 1.11 2004/05/07 11:04:00 rherveille -- Fixed a bug where the core would signal an arbitration lost (AL bit set), when another master controls the bus and the other master generates a STOP bit. -- -- Revision 1.10 2004/02/27 07:49:43 rherveille -- Fixed a bug in the arbitration-lost signal generation. VHDL version only. -- -- Revision 1.9 2003/08/12 14:48:37 rherveille -- Forgot an 'end if' :-/ -- -- Revision 1.8 2003/08/09 07:01:13 rherveille -- Fixed a bug in the Arbitration Lost generation caused by delay on the (external) sda line. -- Fixed a potential bug in the byte controller's host-acknowledge generation. -- -- Revision 1.7 2003/02/05 00:06:02 rherveille -- Fixed a bug where the core would trigger an erroneous 'arbitration lost' interrupt after being reset, when the reset pulse width < 3 clk cycles. -- -- Revision 1.6 2003/02/01 02:03:06 rherveille -- Fixed a few 'arbitration lost' bugs. VHDL version only. -- -- Revision 1.5 2002/12/26 16:05:47 rherveille -- Core is now a Multimaster I2C controller. -- -- Revision 1.4 2002/11/30 22:24:37 rherveille -- Cleaned up code -- -- Revision 1.3 2002/10/30 18:09:53 rherveille -- Fixed some reported minor start/stop generation timing issuess. -- -- Revision 1.2 2002/06/15 07:37:04 rherveille -- Fixed a small timing bug in the bit controller.\nAdded verilog simulation environment. -- -- Revision 1.1 2001/11/05 12:02:33 rherveille -- Split i2c_master_core.vhd into separate files for each entity; same layout as verilog version. -- Code updated, is now up-to-date to doc. rev.0.4. -- Added headers. -- -- ------------------------------------- -- Bit controller section ------------------------------------ -- -- Translate simple commands into SCL/SDA transitions -- Each command has 5 states, A/B/C/D/idle -- -- start: SCL ~~~~~~~~~~~~~~\____ -- SDA XX/~~~~~~~\______ -- x | A | B | C | D | i -- -- repstart SCL ______/~~~~~~~\___ -- SDA __/~~~~~~~\______ -- x | A | B | C | D | i -- -- stop SCL _______/~~~~~~~~~~~ -- SDA ==\___________/~~~~~ -- x | A | B | C | D | i -- --- write SCL ______/~~~~~~~\____ -- SDA XXX===============XX -- x | A | B | C | D | i -- --- read SCL ______/~~~~~~~\____ -- SDA XXXXXXX=XXXXXXXXXXX -- x | A | B | C | D | i -- -- Timing: Normal mode Fast mode ----------------------------------------------------------------- -- Fscl 100KHz 400KHz -- Th_scl 4.0us 0.6us High period of SCL -- Tl_scl 4.7us 1.3us Low period of SCL -- Tsu:sta 4.7us 0.6us setup time for a repeated start condition -- Tsu:sto 4.0us 0.6us setup time for a stop conditon -- Tbuf 4.7us 1.3us Bus free time between a stop and start condition -- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity i2c_master_bit_ctrl is port ( clk : in std_logic; rst : in std_logic; nReset : in std_logic; ena : in std_logic; -- core enable signal (keep in reset if not enabled) clk_en : in std_logic; -- clk_cnt : in unsigned(15 downto 0); -- clock prescale value -- clk_6x_o: out std_logic; -- clock based on clk_en used for debug cmd : in std_logic_vector(3 downto 0); cmd_ack : out std_logic; -- command completed busy : out std_logic; -- i2c bus busy al : out std_logic; -- arbitration lost din : in std_logic; dout : out std_logic; -- i2c lines scl_i : in std_logic; -- i2c clock line input scl_o : out std_logic; -- i2c clock line output scl_oen : out std_logic; -- i2c clock line output enable, active low sda_i : in std_logic; -- i2c data line input sda_o : out std_logic; -- i2c data line output sda_oen : out std_logic -- i2c data line output enable, active low ); end entity i2c_master_bit_ctrl; architecture structural of i2c_master_bit_ctrl is COMPONENT glitch_filter IS -- size = number of consecutive dins that must be the same before sending to dout -- DOUT_RST = reset state of dout GENERIC (SIZE : INTEGER; DOUT_RST : STD_LOGIC ); PORT ( din : IN STD_LOGIC; -- data in dout : OUT STD_LOGIC; -- data out rise : OUT STD_LOGIC; -- single pulse when dout is rising fall : OUT STD_LOGIC; -- single pulse when dout is falling clk : IN STD_LOGIC; -- clock rst_n: IN STD_LOGIC); -- reset - active low END COMPONENT glitch_filter; constant I2C_CMD_NOP : std_logic_vector(3 downto 0) := "0000"; constant I2C_CMD_START : std_logic_vector(3 downto 0) := "0001"; constant I2C_CMD_STOP : std_logic_vector(3 downto 0) := "0010"; constant I2C_CMD_READ : std_logic_vector(3 downto 0) := "0100"; constant I2C_CMD_WRITE : std_logic_vector(3 downto 0) := "1000"; type states is (idle_a, idle_b, idle_c, idle_d, idle_e, idle_f, start_a, start_b, start_c, start_d, start_e, stop_a, stop_b, stop_c, stop_d, stop_e, rd_a, rd_b, rd_c, rd_d, rd_e, wr_a, wr_b, wr_c, wr_d, wr_e); signal c_state : states; signal iscl_oen, isda_oen : std_logic; -- internal I2C lines signal sda_chk : std_logic; -- check SDA status (multi-master arbitration) signal dscl_oen : std_logic; -- delayed scl_oen signals signal sSCL, sSDA : std_logic; -- synchronized SCL and SDA inputs signal sSCL_r, sSDA_r : std_logic; -- synchronized SCL and SDA rising edges signal sSDA_f : std_logic; -- synchronized SDA falling edges --@@@signal dSCL, dSDA : std_logic; -- delayed versions ofsSCL and sSDA signal dSCL, dSDA : std_logic; -- delayed versions ofsSCL and sSDA -- signal clk_en : std_logic; -- statemachine clock enable signal scl_sync, slave_wait : std_logic; -- clock generation signals signal ial : std_logic; -- internal arbitration lost signal signal cnt : unsigned(15 downto 0); -- clock divider counter (synthesis) signal slave_wait_cnt : unsigned(7 downto 0); -- slave wait timeout counter signal slave_wait_to : std_logic; -- slave wait timeout signal clk_6x : std_logic; begin -- whenever the slave is not ready it can delay the cycle by pulling SCL low -- delay scl_oen process (clk, nReset) begin if (nReset = '0') then dscl_oen <= '0'; elsif (clk'event and clk = '1') then dscl_oen <= iscl_oen; end if; end process; -- slave_wait is .asserted when master wants to drive SCL high, but the slave pulls it low -- slave_wait remains asserted until the slave releases SCL or the slave_wait timeout -- counter terminates. process (clk, nReset) begin if (nReset = '0') then slave_wait <= '0'; elsif (clk'event and clk = '1') THEN slave_wait <= ((iscl_oen and not dscl_oen and not sSCL) or (slave_wait and not sSCL)) and not slave_wait_to; end if; end process; -- @@@@ TEST THIS!! @@@@ -- -- Generate slave_wait timeout counter. -- -- If the fiber gets disconnected and sSCL remains low, could run into a -- situation where slave_wait remains asserted indefinitly. Without a -- timeout counter, this could cause the I2C master to lock-up. So this -- timeout counter, counts whenever slave_wait is asserted. The counter -- is initialized to 256*clk_cnt which is 64*scl_period. This should be -- plenty of time for the slowest of slaves but still under 1 second so -- the user does not have to wait too long before it will abort if there -- is a problem. -- -- If slave_wait_to asserts, then slave_wait will be deasserted (above) -- and the arbitration lost (ial) signal will be asserted to reset the -- state machine. gen_slave_wait_to: process(clk, nReset) begin if (nReset = '0') THEN -- initialize to the maximum so no chance that slave_wait_to will -- be asserted out of the gate. slave_wait_cnt <= (others => '1'); slave_wait_to <= '0'; -- timeout flag elsif (clk'event and clk = '1') then if (slave_wait = '0') then -- slave_wait_cnt <= clk_cnt & x"00"; slave_wait_cnt <= x"ff"; slave_wait_to <= '0'; -- timeout flag elsif (slave_wait_cnt = 0) then slave_wait_to <= '1'; -- timeout flag else if(clk_en = '1')then slave_wait_cnt <= slave_wait_cnt - 1; end if; slave_wait_to <= '0'; -- timeout flag end if; end if; end process gen_slave_wait_to; -- master drives SCL high, but another master pulls it low -- master start counting down its low cycle now (clock synchronization) --scl_sync <= dSCL and not sSCL and iscl_oen; -- SDG: With the ngCCM, the I2C clocks are output only. So no need for -- multi-master clock synchronization. Additinally, although unlikely, -- the long latency between output and input, due to the round trip of the -- GBT, could inadventently trip this. So to prevent that from being a -- problem and since don't need the feature anyway, disable the -- multi-master clock synchronization by always deasserting scl_sync. scl_sync <= '0'; -- generate clk enable signal -- gen_clken: process(clk, nReset) -- begin -- if (nReset = '0') then -- cnt <= (others => '0'); -- clk_en <= '1'; -- elsif (clk'event and clk = '1') then -- if ((rst = '1') or (cnt = 0) or (ena = '0') or (scl_sync = '1')) then -- cnt <= clk_cnt; -- clk_en <= '1'; ----!! elsif (slave_wait = '1') then ----!! cnt <= cnt; ----!! clk_en <= '0'; -- else -- cnt <= cnt -1; -- clk_en <= '0'; -- end if; -- end if; -- end process gen_clken; -- generate clk_6x debug signal directly from clk_en. Use clk_en -- so can use clk_6x in chip scope to clock in the direct result -- when clk_en is asserted. -- gen_clk6x: process(clk, nReset) -- begin -- if (nReset = '0') then -- clk_6x <= '1'; -- elsif (clk'event and clk = '1') then -- if (clk_en = '1') then -- clk_6x <= NOT clk_6x; -- toggle clk_6x when clk_en is asserted -- end if; -- end if; -- end process gen_clk6x; -- clk_6x_o <= clk_6x; -- output -- generate bus status controller bus_status_ctrl: block --signal cSCL, cSDA : std_logic_vector( 1 downto 0); -- capture SDA and SCL --signal fSCL, fSDA : std_logic_vector( 2 downto 0); -- filter inputs for SCL and SDA --signal filter_cnt : unsigned(13 downto 0); -- clock divider for filter signal sta_condition : std_logic; -- start detected signal sto_condition : std_logic; -- stop detected signal cmd_stop : std_logic; -- STOP command signal ibusy : std_logic; -- internal busy signal begin gf_scl : glitch_filter GENERIC MAP (SIZE => 8, DOUT_RST => '1') PORT MAP (din => scl_i, dout => sSCL, rise => sSCL_r, fall => OPEN, clk => clk, rst_n => nReset); gf_sda : glitch_filter GENERIC MAP (SIZE => 8, DOUT_RST => '1') PORT MAP (din => sda_i, dout => sSDA, rise => sSDA_r, fall => sSDA_f, clk => clk, rst_n => nReset); -- detect start condition => detect falling edge on SDA while SCL is high -- detect stop condition => detect rising edge on SDA while SCL is high detect_sta_sto: process(clk, nReset) begin if (nReset = '0') then sta_condition <= '0'; sto_condition <= '0'; elsif (clk'event and clk = '1') then if (rst = '1' or ena = '0') then sta_condition <= '0'; sto_condition <= '0'; else sta_condition <= sSDA_f and sSCL; sto_condition <= sSDA_r and sSCL; --sta_condition <= (not sSDA and dSDA) and sSCL; --sto_condition <= (sSDA and not dSDA) and sSCL; end if; end if; end process detect_sta_sto; -- generate i2c-bus busy signal gen_busy: process(clk, nReset) begin if (nReset = '0') then ibusy <= '0'; elsif (clk'event and clk = '1') then if (rst = '1' or ena = '0') then ibusy <= '0'; else ibusy <= (sta_condition or ibusy) and not sto_condition; end if; end if; end process gen_busy; busy <= ibusy; -- generate arbitration lost signal -- aribitration lost when: -- 1) master drives SDA high, but the i2c bus is low -- 2) stop detected while not requested (detect during 'idle' state) gen_al: process(clk, nReset) begin if (nReset = '0') then cmd_stop <= '0'; ial <= '0'; elsif (clk'event and clk = '1') then if (rst = '1' or ena = '0') then cmd_stop <= '0'; ial <= '0'; else if (clk_en = '1') then if (cmd = I2C_CMD_STOP) then cmd_stop <= '1'; else cmd_stop <= '0'; end if; end if; if (c_state = idle_a) then ial <= (sda_chk and not sSDA and isda_oen) or slave_wait_to or (sto_condition and not cmd_stop); else ial <= (sda_chk and not sSDA and isda_oen) or slave_wait_to; end if; end if; end if; end process gen_al; al <= ial; -- generate dout signal, store dout on rising edge of SCL gen_dout: process(clk, nReset) begin if (nReset = '0') then dout <= '0'; elsif (clk'event and clk = '1') then if (sSCL_r = '1') then --if (sSCL = '1' and dSCL = '0') then dout <= sSDA; end if; end if; end process gen_dout; end block bus_status_ctrl; -- generate statemachine nxt_state_decoder : process (clk, nReset) begin if (nReset = '0') then c_state <= idle_b; cmd_ack <= '0'; iscl_oen <= '1'; isda_oen <= '1'; sda_chk <= '0'; elsif (clk'event and clk = '1') then -- if (rst = '1' or ial = '1' or ena = '0') then if (rst = '1' or ena = '0') then c_state <= idle_b; cmd_ack <= '0'; iscl_oen <= '1'; isda_oen <= '1'; sda_chk <= '0'; else cmd_ack <= '0'; -- default no acknowledge if (clk_en = '1') then case (c_state) is -- idle when idle_f => case cmd is when I2C_CMD_START => c_state <= start_a; when I2C_CMD_STOP => c_state <= stop_a; when I2C_CMD_WRITE => c_state <= wr_a; when I2C_CMD_READ => c_state <= rd_a; when others => c_state <= idle_a; -- NOP command end case; -- iscl_oen <= iscl_oen; -- keep SCL in same state iscl_oen <= '0'; -- keep SCL low IF (cmd = I2C_CMD_WRITE) THEN -- If a write, get a jump on SCL and send -- din so that it is set for at least 2 -- clk_en's before SCL goes high. This will -- help with clocking margins. isda_oen <= din; -- set SDA ELSIF (cmd = I2C_CMD_READ) THEN -- If a read, get a jump on SCL and free -- so that slave can set line isda_oen <= '1'; -- tri-state SDA ELSE isda_oen <= isda_oen; -- keep SDA in same state END IF; iscl_oen <= '0'; -- keep SCL low sda_chk <= '0'; -- don't check SDA when idle_a => c_state <= idle_b; iscl_oen <= '0'; -- keep SCL low when idle_b => c_state <= idle_c; iscl_oen <= '1'; -- set SCL high when idle_c => c_state <= idle_d; iscl_oen <= '1'; -- keep SCL high when idle_d => c_state <= idle_e; iscl_oen <= '1'; -- keep SCL high when idle_e => c_state <= idle_f; iscl_oen <= '0'; -- set SCL low -- cmd_ack <= '1'; -- command completed ?? check -- start when start_a => c_state <= start_b; iscl_oen <= '0'; -- keep SCL low -- iscl_oen <= iscl_oen; -- keep SCL in same state (for repeated start) isda_oen <= '1'; -- set SDA high sda_chk <= '0'; -- don't check SDA when start_b => c_state <= start_c; iscl_oen <= '1'; -- set SCL high isda_oen <= '1'; -- keep SDA high sda_chk <= '0'; -- don't check SDA when start_c => c_state <= start_d; iscl_oen <= '1'; -- keep SCL high isda_oen <= '0'; -- set SDA low sda_chk <= '0'; -- don't check SDA when start_d => c_state <= start_e; iscl_oen <= '1'; -- keep SCL high isda_oen <= '0'; -- keep SDA low sda_chk <= '0'; -- don't check SDA when start_e => c_state <= idle_f; cmd_ack <= '1'; -- command completed iscl_oen <= '0'; -- set SCL low isda_oen <= '0'; -- keep SDA low sda_chk <= '0'; -- don't check SDA -- stop when stop_a => c_state <= stop_b; iscl_oen <= '0'; -- keep SCL low isda_oen <= '0'; -- set SDA low sda_chk <= '0'; -- don't check SDA when stop_b => c_state <= stop_c; iscl_oen <= '1'; -- set SCL high isda_oen <= '0'; -- keep SDA low sda_chk <= '0'; -- don't check SDA when stop_c => c_state <= stop_d; iscl_oen <= '1'; -- keep SCL high isda_oen <= '0'; -- keep SDA low sda_chk <= '0'; -- don't check SDA when stop_d => c_state <= stop_e; iscl_oen <= '1'; -- keep SCL high isda_oen <= '1'; -- set SDA high sda_chk <= '0'; -- don't check SDA when stop_e => -- SDG: Added this state because kept running into the issue where the extended latency -- of GBT would make the stop condition not be seen until after the STOP CMD ack'ed at -- stop_d. It now ack's at stop_e to give one more clk_en period. This also makes stop -- symmetric with start, read and write as far as number of states per command. -- -- Considered making this watch for the stop condition and wait until it returns from -- GBT. However, if there is no GBT comm., need to add a timeout timer to catch or else -- stuck forever. Since this added state makes stop consistent with the other cmds in -- the number of states, that seems to be the simplest and best solution. c_state <= idle_f; cmd_ack <= '1'; -- command completed iscl_oen <= '1'; -- keep SCL high isda_oen <= '1'; -- keep SDA high sda_chk <= '0'; -- don't check SDA -- read when rd_a => c_state <= rd_b; iscl_oen <= '0'; -- keep SCL low isda_oen <= '1'; -- tri-state SDA sda_chk <= '0'; -- don't check SDA when rd_b => c_state <= rd_c; iscl_oen <= '1'; -- set SCL high isda_oen <= '1'; -- tri-state SDA sda_chk <= '0'; -- don't check SDA when rd_c => c_state <= rd_d; iscl_oen <= '1'; -- keep SCL high isda_oen <= '1'; -- tri-state SDA sda_chk <= '0'; -- don't check SDA when rd_d => -- SDG: Added this state so that the clock is divided by an even number to keep -- the duty cycle of SCL close to 50%. As it was before, it was divided by 5 -- making it 40/60%. This made ~80kHz the maximum clock because it created a -- positive pulse width 5 us, which is the max if 100kHz is the maximum SCL. c_state <= rd_e; iscl_oen <= '1'; -- keep SCL high isda_oen <= '1'; -- tri-state SDA sda_chk <= '0'; -- don't check SDA when rd_e => c_state <= idle_f; cmd_ack <= '1'; -- command completed iscl_oen <= '0'; -- set SCL low isda_oen <= '1'; -- tri-state SDA sda_chk <= '0'; -- don't check SDA -- write when wr_a => c_state <= wr_b; iscl_oen <= '0'; -- keep SCL low isda_oen <= din; -- set SDA sda_chk <= '0'; -- don't check SDA (SCL low) when wr_b => c_state <= wr_c; iscl_oen <= '1'; -- set SCL high isda_oen <= din; -- keep SDA sda_chk <= '0'; -- don't check SDA yet -- Allow some more time for SDA and SCL to settle when wr_c => c_state <= wr_d; iscl_oen <= '1'; -- keep SCL high isda_oen <= din; -- keep SDA sda_chk <= '0'; -- don't check SDA yet -- Allow some more time for SDA and SCL to settle when wr_d => -- SDG: Added this state so that the clock is divided by an even number to keep -- the duty cycle of SCL close to 50%. As it was before, it was divided by 5 -- making it 40/60%. This made ~80kHz the maximum clock because it created a -- positive pulse width 5 us, which is the max if 100kHz is the maximum SCL. c_state <= wr_e; iscl_oen <= '1'; -- keep SCL high isda_oen <= din; -- keep SDA sda_chk <= '1'; -- check SDA when wr_e => c_state <= idle_f; cmd_ack <= '1'; -- command completed iscl_oen <= '0'; -- set SCL low isda_oen <= din; -- keep SDA sda_chk <= '0'; -- don't check SDA (SCL low) when others => end case; end if; end if; end if; end process nxt_state_decoder; -- assign outputs scl_o <= '0'; scl_oen <= iscl_oen; sda_o <= '0'; sda_oen <= isda_oen; end architecture structural;