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:
This package is available from https://szofi.net/pub/verilog/mmio_i2c/. Select the mmio_i2c-latest.tar.gz file for the latest version. Comments are welcomed! Contact information: see ./core/mmio_i2c.v.
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.
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.
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];
end
endmodule
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 valueAddress 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 1OAR[7:1]
: own address Address 7: OMR
: own address mask register:
OMR[0]
: don't care, just a 1-bit memoryOMR[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. Being in receiver or in transmitter mode is determined by the software as follows:
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:
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. 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. RXCNTR
is reached zero. Software should issue it manually then, or perform a repeated START if needed. 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.
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. 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.ISCR[*]
bits). This aspect needs to have further clarification.[7:0]
are used.