Below I provide the entire source code for a FPGA I2C Slave implementation in VHDL. To learn more about the implementation's design and FSM, see this post here.
This I2C Slave implementation provides basic read, write, and addressing functionality. It also supports repeated start conditions. The address of the slave is configurable through a generic. Note that this implementation does not currently support "advanced" features such as clock stretching. It also does not support 10-bit addressing, although I'd imagine it wouldn't be too hard to implement this.
This implementation has been tested on an Altera Cyclone IV FPGA. A Raspberry Pi 2 was used as the I2C master.
How to use
The I2C Slave FSM provides the following interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
--Transaction is in progress in_progress : out std_logic; --READ command signals tx_done : out std_logic; tx_byte : in std_logic_vector(7 downto 0); --WRITE command signals rx_byte : out std_logic_vector(7 downto 0); rx_data_rdy : out std_logic; --System clock clk : in std_logic); |
tx_done
goes high when the I2C Slave module has finished transmitting data to the master. In other words, it signals the completion of a READ command (master reads from slave). The data to be sent to the master must be set in tx_byte
.
rx_data_rdy
goes high when data is received from the master. In other words, it indicates the completion of a WRITE command (master writes to slave). The received data can be found in rx_byte
.
clk
is the clock signal for the I2C Slave module itself. This should not be confused with SCL, which is the I2C-bus clock line. For best performance, clk
should be significantly faster than SCL.
Source Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
---------------------------------------------------- -- I2C Slave FSM ---------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity i2c_slave_fsm is generic ( SLAVE_ADDR : std_logic_vector(6 downto 0)); port ( --I2C IO Signals scl : inout std_logic; sda : inout std_logic; --Transaction is in progress in_progress : out std_logic; --READ command signals tx_done : out std_logic; tx_byte : in std_logic_vector(7 downto 0); --WRITE command signals rx_byte : out std_logic_vector(7 downto 0); rx_data_rdy : out std_logic; --System clock clk : in std_logic); end i2c_slave_fsm; architecture i2c_slave_fsm of i2c_slave_fsm is --Output signals signal sda_out_en : std_logic := '0'; signal sda_o : std_logic := '0'; signal rx_data_rdy_reg : std_logic := '0'; signal tx_byte_buf : std_logic_vector(7 downto 0) := (others => '0'); --Pipelined SDA and SCL signals signal scl_d : std_logic := '1'; signal scl_d2 : std_logic := '1'; signal sda_d : std_logic := '1'; signal sda_d2 : std_logic := '1'; --Strobes for SCL edges and Start/Stop bits signal start_strobe : std_logic := '0'; signal stop_strobe : std_logic := '0'; signal scl_rising_strobe : std_logic := '0'; signal scl_falling_strobe : std_logic := '0'; --I2C FSM control signals type state_t is (IDLE, READ_ADDRESS, SEND_ACK_1, WRITE_CMD, READ_CMD, WAIT_ACK_1, WAIT_ACK_2, WAIT_STOP); signal state : state_t := IDLE; signal rw_command : std_logic := '0'; --Read or write command signal bit_counter : integer range 0 to 8 := 0; --Counts number of bits per transaction signal continue_read : std_logic := '0'; --Signals for storing address/data from master signal addr_buf : std_logic_vector(6 downto 0) := (others => '0'); signal rx_data_buf : std_logic_vector(7 downto 0) := (others => '0'); begin --I2C State Machine i2c_fsm_p : process (clk) is begin if rising_edge(clk) then -- Default values sda_o <= '0'; sda_out_en <= '0'; rx_data_rdy_reg <= '0'; case state is --Idle state (functionally same as STOP) when IDLE => if start_strobe = '1' then state <= READ_ADDRESS; bit_counter <= 0; end if; --Reading 7 bit address from master when READ_ADDRESS => if scl_rising_strobe = '1' then if bit_counter < 7 then --Store the first 7 bits that are read into the address buffer bit_counter <= bit_counter + 1; addr_buf(6-bit_counter) <= sda_d; elsif bit_counter = 7 then --The next bit indicates whether it is a read or write command bit_counter <= bit_counter + 1; rw_command <= sda_d; end if; end if; if bit_counter = 8 and scl_falling_strobe = '1' then bit_counter <= 0; --Check if the address from the master matches the slave address. If it does, --acknowledge, otherwise wait for stop bit. if addr_buf = SLAVE_ADDR then state <= SEND_ACK_1; if rw_command = '1' then tx_byte_buf <= tx_byte; end if; else state <= IDLE; end if; end if; --Send ACK bit by holding SDA low through one SCL cycle when SEND_ACK_1 => sda_out_en <= '1'; sda_o <= '0'; if scl_falling_strobe = '1' then if rw_command = '0' then state <= WRITE_CMD; else state <= READ_CMD; end if; end if; --Waiting for acknowledge from master when WAIT_ACK_1 => if scl_rising_strobe = '1' then state <= WAIT_ACK_2; if sda_d = '1' then --NACK received, stop transmission and wait for STOP bit continue_read <= '0'; else --ACK received, continue transmission continue_read <= '1'; tx_byte_buf <= tx_byte; end if; end if; --Waiting for acknowledge from master --Requires two states to wait for the entire SCL cycle to pass when WAIT_ACK_2 => if scl_falling_strobe = '1' then if continue_read = '1' then if rw_command = '0' then state <= WRITE_CMD; else state <= READ_CMD; end if; else state <= WAIT_STOP; end if; end if; --Write command (write from master to slave) when WRITE_CMD => --On each rising edge, read the data bit on the SDA line and store it --in rx_data_buf if scl_rising_strobe = '1' then if bit_counter <= 7 then rx_data_buf(7-bit_counter) <= sda_d; bit_counter <= bit_counter + 1; end if; if bit_counter = 7 then rx_data_rdy_reg <= '1'; end if; end if; --When the byte is done, send an ACK if scl_falling_strobe = '1' and bit_counter = 8 then state <= SEND_ACK_1; bit_counter <= 0; end if; --Read command (Master reads data from slave) when READ_CMD => sda_out_en <= '1'; sda_o <= tx_byte_buf(7-bit_counter); --The SDA line must be set before the rising edge of SCL. --Therefore, set it on the falling edge of the previous SCL cycle if scl_falling_strobe = '1' then if bit_counter < 7 then bit_counter <= bit_counter + 1; elsif bit_counter = 7 then --Wait for an ACK after sending a byte state <= WAIT_ACK_1; bit_counter <= 0; end if; end if; --NACK receiving during Read command, wait for stop bit when WAIT_STOP => tx_done <= '1'; null; end case; --Reset when a transmission stops or starts if start_strobe = '1' then tx_done <= '0'; bit_counter <= 0; state <= READ_ADDRESS; end if; if stop_strobe = '1' then tx_done <= '0'; bit_counter <= 0; state <= IDLE; end if; end if; end process; --Strobe logic strobe_p : process (clk) is begin if rising_edge(clk) then --Pipelined SDA and SCL signals scl_d <= scl; scl_d2 <= scl_d; sda_d <= sda; sda_d2 <= sda_d; --Logic for SCL edge detection strobes scl_rising_strobe <= '0'; scl_falling_strobe <= '0'; if scl_d2 = '0' and scl_d = '1' then scl_rising_strobe <= '1'; end if; if scl_d2 = '1' and scl_d = '0' then scl_falling_strobe <= '1'; end if; --Logic for Start and stop bit detection start_strobe <= '0'; stop_strobe <= '0'; if scl_d = '1' and scl_d2 = '1' and sda_d2 = '1' and sda_d = '0' then start_strobe <= '1'; stop_strobe <= '0'; end if; if scl_d2 = '1' and scl_d = '1' and sda_d2 = '0' and sda_d = '1' then start_strobe <= '0'; stop_strobe <= '1'; end if; end if; end process; --SDA and SCL outputs sda <= sda_o when sda_out_en = '1' else 'Z'; scl <= 'Z'; --Local unit outputs rx_data_rdy <= rx_data_rdy_reg; rx_byte <= rx_data_buf; --In progress signal in_progress <= '0' when state = IDLE else '1'; end i2c_slave_fsm; |