wb_i2c_slave – An I2C to Wishbone Bridge for FPGAs

This is the project homepage for wb_i2c_slave, an I2C slave device implementation that acts as a master for the Wishbone bus. The engine was designed for FPGAs and written in VHDL.

Overview

The wb_i2c_slave is an I2C slave implementation for FPGAs that translates I2C requests to the Wishbone bus. It allows clients to generate cycles on the Wishbone bus in an FPGA design through the I2C protocol. The design is supposed to be configurable through generics, but there hasn’t been a lot of test coverage for values other than the defaults.

The module’s internal architecture is divided into an I2C bus sampling logic and a Wishbone master interface logic.

The module doesn’t impose a maximum speed requirement on the I2C clock. Because the module samples the I2C clock based on its own logic clock, the maximum I2C speed is determined by the ratio between I2C clock and logic clock. Internally, the I2C clock is not only sampled but also debounced. Sampling occurs at the speed of the logic clock; debouncing works through an eight-bit shift register. In effect, the logic clock should run faster than the I2C clock by at least a factor of 16. Higher factors will work better.

Usage

Using the wb_i2c_slave component in your VHDL design is as easy as declaring and instantiating any other component. Below is the VHDL entity declaration of the component.

entity wb_i2c_slave is
 generic (
     dat_sz  : natural := 8;
      off_sz  : natural := 8;
     pg_sz   : natural := 1;
     increment_target : std_logic := '1';
     i2c_adr : std_logic_vector(7 downto 0) := x"A2"
 );
 port (
     clk_i       : in  std_logic;
     rst_i       : in  std_logic;
     --
     -- Whishbone Interface
     --
     adr_o       : out std_logic_vector((off_sz - 1) downto (pg_sz - 1));
     dat_i       : in  std_logic_vector((dat_sz - 1) downto 0);
     dat_o       : out std_logic_vector((dat_sz - 1) downto 0);
     ack_i       : in  std_logic;
     cyc_o       : out std_logic;
     stall_i     : in  std_logic;
     err_i       : in  std_logic;
     lock_o      : out std_logic;
     rty_i       : in  std_logic;
     sel_o       : out std_logic_vector((pg_sz - 1) downto 0);
     stb_o       : out std_logic;
     we_o        : out std_logic;
     --
     -- I2C Interface
     --
     i2c_scl_io  : inout std_logic;
     i2c_sda_io  : inout std_logic
 );
 end wb_i2c_slave;

The design is customizable through a few generics, which are described in the following table.

GenericDescription
dat_szWidth of Wishbone data bus. This also determines the size of the internal I2C registers. The module will probably not work if this generic is set to a value other than 8.
off_szDetermines the width Wishbone address bus. This also affects the width of the I2C address register.
pg_szDetermines the page size of the Wishbone bus, therefore the granulatiry of addressing. Given in powers of two, i.e. one page contains 2**pg_sz nibbles (size of nibble == dat_sz). The module will probably not work correctly if this is set to something other than 1.
increment_targetIncrement Target Register. If set, consecutive read or write access cycles will increment the internal target register by one.
i2c_adrI2C bus address of the device.

Registers

The module does not have user accessible registers.

I2C Behavior

An example I2C write cycle sequence understood by this module looks like this:

[START], [Device Address w/ Write Bit], [Wishbone Address MSB], ..., [Wishbone Address LSB], [Data Byte #0], [Data Byte #1], ..., [STOP]

If the increment_target generic is set to 1, this will generate a write cycle writing [Data Byte #0] to Wishbone address [MSB..LSB] followed by a write cycle writing [Data Byte #1] to the Wishbone address ([MSB..LSB] + 1), etc.

If the increment_target generic is set to 0, this will generate a write cycle writing [Data Byte #0] to Wishbone address [MSB..LSB] followed by a write cycle writing [Data Byte #1] to the same Wishbone address.

An example I2C read cycle sequence understood by this module looks like this:

[START], [Device Address w/ Write Bit], [Wishbone Address MSB], ..., [Wishbone Address LSB], [Repeated START], [Device Address w/ Read Bit], [Data Byte #0], [Data Byte #1], ..., [STOP]

If the increment_target generic is set to 1, this will generate a read cycle reading from Wishbone address [MSB..LSB]returning [Data Byte #0] followed by a read cycle from the Wishbone address ([MSB..LSB] + 1) returning [Data Byte #1], etc.

If the increment_target generic is set to 0, this will generate read cycles repeatedly reading from Wishbone address [MSB..LSB] returning [Data Byte #0] followed by returning [Data Byte #1], etc.

The device will stall the I2C clock while Wishbone transfers are in progress.

Wishbone Behavior

The module acts as a Wishbone master. It generates classic read/write cycles and does not support classic pipelined read/write operations. Thus, it does not honor the stall signal which may be generated by slaves.

The Wishbone cyc signal is asserted as soon as the I2C logic has decoded the I2C address and determined that the module is being targeted. It is released once the I2C transfer is complete and the logic has entered the idle state again. This behavior is useful if the Wishbone interface is connected to an actual bus with multiple master/slave participants which requires an arbiter. Keeping the cyc signal asserted ensures that the Wishbone bus is held exclusively while the I2C transfer is in progress, i.e. no other Wishbone master can issue transfers in parallel.

The Wishbone stb signal is asserted only while a Wishbone transfer is in progress. The signal is being kept active until the Wishbone master logic has seen a response from a slave, i.e. either an ack or an error response.

Caveats

The module does not contain any kind of watchdog logic. Hence, the described I2C and Wishbone behavior can effectively lock up all Wishbone logic if a Wishbone slave fails to generate a response to a generated Wishbone cycle. This is the case if an I2C transfer attempts to target a Wishbone address to which no slave is registered.

Because the device stalls the I2C clock while Wishbone transfers are in progress, such a situation can also block the I2C bus.

Resolving this situation is outside the scope of the wb_i2c_slave logic. If the behavior is dangerous or undesirable in your logic design, I suggest you resolve it in the Wishbone interconnect logic, e.g. by building watchdog logic into your Wishbone interconnect.

Download

Below are the links to the VHDL files.

Links

Here are a few links related to this project.