Project maintainers


Name: mmio_i2c
Created: Apr 26, 2024
Updated: Apr 27, 2024
SVN: No files checked in
Bugs: 0 reported / 0 solved
Star0you like it: star it!

Other project properties

Category:Communication controller
Development status:Stable
Additional info:
WishBone compliant: No
WishBone version: n/a
License: LGPL

MMIO I2C - An I2C peripheral with memory-mapped input/output register set


This core is intended to be a fully fledged I2C core, aimed to provide functionalities for software-controlled master (including multi-master) and slave device operation modes. In the current (and most likely in the future) version of this core, the I2C operations are controlled via eight, 8-bit wide synchronous registers while the core have auxiliary outputs providing input wires for interrupt controllers and DMA engines on demand.

Some of the main features of the core:

  • 7-bit addressing mode
  • built-in baud rate generator, clock synchronization and clock stretching
  • synchronous update of the status bits
  • optional digital filter
  • support for multi-master modes
  • a single own address register with address mask register support


This package is available from Select the mmio_i2c-latest.tar.gz file for the latest version. Comments are welcomed! Contact information: see ./core/mmio_i2c.v.

Theory of operations

This I2C core is built around a single block of a fully synchronous state machine, providing an easy overview for analyzing the changes implied by the actions on the I2C bus. The implementation essentially a loosely coupled interaction of a "slave engine" (responsible for driving the SDA line and stretching the SCL line if data to be transmitted are not available), a "clock generator" (that is an engine performing delayed pull-down and release of the SCL line where the delay time is defined by a baud rate generator) and a "master initiator" (which stretches down the SDA line if the bus is considered free). The "slave engine" then detect the start/stop conditions and the changes on the SCL line fully independently from the state of the "clock generator" and "master initiator", thus the same logic drives the SDA line both in master and slave operation modes and allows the synchronization of the clocks in the case of multiple active masters during address or write phases on the bus.

Interfacing towards the I2C bus

On both physical lines, the interface towards the I2C bus is implemented as a combination of an input and an output wire: The bus level sensed via the input line while the bus is pulled down if output line is 0 (otherwise the bus is not pulled down). Most of the FPGA vendors require designated I/O block cells to provide the interconnect with the package pin using these types of input + output combinations. A digital interconnect of multiple such cores (within a single module) via the same I2C bus is therefore implemented by a wired-and logic of the outputs.

Interfacing towards the digital logic

Eight, 8-bit synchronous registers are provided by the core for interfacing towards the rest of the digital logic. Registers are therefore addressed via a 3-bit wide address bus, in a similar manner like a memory primitive of

module mmio_interface(input we,input re,input [2:0] a,output reg [7:0] do,input [7:0] di)
        reg [7:0] mem [0:7];
        always @(posedge clk) begin
                if ( we )
                        mem[a] <= di;
                else if ( re )
                        do <= mem[a];

where the memory is connected to the rest of the logic via the (we,re,a,do,di) lines and write operation have a priority over read operation. Wait states are not needed for operations, both write and read access are considered successful after the next cycle. The registers are the following:

Address 0: CR, control register, write only:

  • CR[0]: writing 1 initiates master mode operations with a START condition as soon as the bus becomes idle or sends a repeated START condition instead of transmitting a byte if transmit data register is empty.
  • CR[1]: writing 1 sends a STOP condition if transmit data register is empty.
  • CR[2]: writing 1 sets the NACK slot to 1 for the last byte to be transmitted in the case of master receive operations.
  • CR[3]: writing 1 clears the NACK register.

Address 1: BRR, baud rate register:

  • BRR[7:0]: sets the "clock low" and "clock high" durations to BRR clock cycles. The effective baud rate is therefore the half of the clock frequency divided by this value

Address 2: ISCR, interrupt state and clear register:

  • ISCR[0]: A START condition successfully sent after detecting an idle state on the bus, meaningful only in master mode. In master mode, clearing this bit (i.e. writing 1 to ISCR[0]) initiates the "clockwork", therefore this clearing is mandatory for proper operations.
  • ISCR[1]: A STOP condition has been detected (slave mode) or successfully been transmitted (in master mode). One could clear this bit, but it is not that necessary for proper operations.
  • ISCR[2]: data have been received and stored in DR. Reading data from DR clears this bit. This bit cannot be cleared by writing 1.
  • ISCR[3]: engine is ready for transmission, data are expected to be written in DR. Writing to DR clears this bit. This bit cannot be cleared by writing 1 into it. SCL is stretched until a byte is written to DR or, in master mode, a repeated START or a STOP control command is sent via writing 1 to CR[0] or CR[1].
  • ISCR[4]: arbitration is lost after transmitting a byte.
  • ISCR[5]: the accumulated value of the NACK bits from the bus after the last clear of this bit. Operations of a master mode after receiving a NACK flag here is possible, but not really recommended. This bit is updated only after transmissions and therefore not related
  • ISCR[6]: own address has been detected. Finding this bit to be 1 initiates the slave mode for the external logic or software.
  • ISCR[7]: reserved for future usage, it will likely be an error bit (e.g. detecting unexpected repeated START or STOP conditions).

Address 3: IER: interrupt enable register:

  • IER[7:0]: the interrupt output of this I2C core is driven to to 1 if (IER&ISCR) is non-zero.

Address 4: DR: data register:

  • DR[7:0]: writing to this register is possible if ISCR[3] is 1 or if the bus is not acquired by this core, otherwise write operation yields unexpected changes. Reading from this register is possible if ISCR[2] is 1, otherwise garbage is read from the register.

Address 5: RXCNTR: receive count register:

  • RXCNTR[7:0]: the number of bytes to be read via this I2C core in master receiver mode.

Address 6: OAR: own address register:

  • OAR[0]: slave operations are enabled if this bit is 1
  • OAR[7:1]: own address

Address 7: OMR: own address mask register:

  • OMR[0]: don't care, just a 1-bit memory
  • OMR[7:1]: own address mask register. Own address is recognized if (OAR[7:1] & ~OMR[7:1]) == (A & ~OMR[7:1]) where A == A[6:0] is the address read after the START condition.

Application notes

Being in receiver or in transmitter mode is determined by the software as follows:

  • In master mode, DR needs to be written as soon as a START condition is detected in ISCR[0] and the clockwork is initiated by clearing this bit. This first transmission determines the further direction of the master mode: if DR[0] has been set to 0, the master stays in transmitter mode (and expects data in DR). If DR[0] has been set to 1, the master is going to be in receiver mode.
  • In slave mode, the first byte received after an address match condition is detected (via ISCR[6]) is the own address, and DR[0] determines the further direction of the slave operations. If DR[0] is found to be 0, the core stays in slave receiver mode, notifying the software/driver via ISCR[2] that there is data to be read from DR. If the first DR[0] is found to be 1, the core enters to slave mode, expecting data to be written to DR once ISCR[3] is 1 (otherwise SCL is stretched down until DR is written).

The operations in master receiver mode is intended to be implemented by software as follows:

  • If the number of bytes to be received is known in advance and it is smaller than or equal to 255, write this non-zero value to RXCNTR. Then, the master clockwork will clock in the data from the slave and notify the software via ISCR[2] if transfer is completed. If NACK is set to 1 by writing 1 into CR[2], the reception of the last byte will be followed by a NACK, otherwise it is ACK'ed.
  • If the number of bytes to be received is not (really) known in advance, write 1 (or some small number) to RXCNTR and clear the NACK via writing 1 into CR[3]. If sufficient amount of bytes are received, repeat this step, or proceed with the previous one with the NACK.
  • A STOP condition is not sent automatically if RXCNTR is reached zero. Software should issue it manually then, or perform a repeated START if needed.
  • Therefore, in master receiver mode, SCL is stretched down until RXCNTR is written to some non-zero value or a STOP or repeated START condition is not issued by writing 1.

Discovery of I2C bus slaves can easily be implemented by checking the NACK bit in ISCR[5] after the first DR transaction and then sending a STOP condition immediately. If ISCR[5] is 0, then the slave with the given address is present on the bus, otherwise no response of any slave has been received.

DMA operations are intended to be implemented by hardware/software as using ISCR[2] and ISCR[3] bits as DMA triggers. Check the corresponding dma_rx and dma_tx assignments in the source code.

Known issues

  • The rst line is currently not implemented, like any other means of a "software reset". One or some of the bits in CR[7:4] are reserved for this purpose.
  • Further tests are needed to properly validate the arbitration loss condition, especially for the infamous master lost arbitration during address phase but addressed as a slave at the same time case.
  • Not all of the registers can be read via the memory interface. Now only ISCR, IER, OAR, OMR can be read (besides DR, of course). BRR and RXCNTR are write-only registers. Therefore, software should count independently of RXCNTR how many bytes have already been read already by the master.
  • This core is unable to detect prolonged SCL stretching.
  • Another types of bus errors are also not detected (but may be detected indirectly via highly unexpected states via the ISCR[*] bits). This aspect needs to have further clarification.
  • The module has 32-bit wide I/O registers, but only the lower 8 bits, [7:0] are used.
  • The length digital filter is configurable only during synthesis time, it cannot be altered via register I/O.