Skip to content

Commit

Permalink
✨ add new module: device-mode I²C controller ("TWD") (#1121)
Browse files Browse the repository at this point in the history
  • Loading branch information
stnolting authored Dec 15, 2024
2 parents 5bde2af + b0fdc76 commit 3d986cb
Show file tree
Hide file tree
Showing 24 changed files with 1,336 additions and 79 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mimpid = 0x01040312 -> Version 01.04.03.12 -> v1.4.3.12

| Date | Version | Comment | Ticket |
|:----:|:-------:|:--------|:------:|
| 14.12.2024 | 1.10.7.4 | :sparkles: add new module: I2C-compatible **Two-Wire Device Controller (TWD)** | [#1121](https://github.com/stnolting/neorv32/pull/1121) |
| 14.12.2024 | 1.10.7.3 | :warning: rework TRNG (change HAL; remove interrupt) | [#1120](https://github.com/stnolting/neorv32/pull/1120) |
| 12.12.2024 | 1.10.7.2 | add external memory configuration/initialization options to testbench | [#1119](https://github.com/stnolting/neorv32/pull/1119) |
| 11.12.2024 | 1.10.7.1 | :test_tube: shrink bootloader's minimal ISA (`rv32e`) and RAM (256 bytes) requirements | [#1118](https://github.com/stnolting/neorv32/pull/1118) |
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ allows booting application code via UART or from external SPI flash
([UART](https://stnolting.github.io/neorv32/#_primary_universal_asynchronous_receiver_and_transmitter_uart0),
[SPI](https://stnolting.github.io/neorv32/#_serial_peripheral_interface_controller_spi) (SPI host),
[SDI](https://stnolting.github.io/neorv32/#_serial_data_interface_controller_sdi) (SPI device),
[TWI/I²C](https://stnolting.github.io/neorv32/#_two_wire_serial_interface_controller_twi),
[TWI](https://stnolting.github.io/neorv32/#_two_wire_serial_interface_controller_twi) (I²C host),
[TWD](https://stnolting.github.io/neorv32/#_two_wire_serial_device_controller_twd) (I²C device),
[ONEWIRE/1-Wire](https://stnolting.github.io/neorv32/#_one_wire_serial_interface_controller_onewire))
* general purpose IOs ([GPIO](https://stnolting.github.io/neorv32/#_general_purpose_input_and_output_port_gpio)) and
[PWM](https://stnolting.github.io/neorv32/#_pulse_width_modulation_controller_pwm)
Expand Down
16 changes: 13 additions & 3 deletions docs/datasheet/soc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ image::neorv32_processor.png[align=center]
<<_secondary_universal_asynchronous_receiver_and_transmitter_uart1,**UART1**>>) with optional hardware flow control (RTS/CTS)
* _optional_ serial peripheral interface host controller (<<_serial_peripheral_interface_controller_spi,**SPI**>>) with 8 dedicated CS lines
* _optional_ 8-bit serial data device interface (<<_serial_data_interface_controller_spi,**SDI**>>)
* _optional_ two wire serial interface controller (<<_two_wire_serial_interface_controller_twi,**TWI**>>), compatible to the I²C standard
* _optional_ two-wire serial interface controller (<<_two_wire_serial_interface_controller_twi,**TWI**>>), compatible to the I²C standard
* _optional_ two-wire serial device controller (<<_two_wire_serial_device_controller_twd,**TWD**>>), compatible to the I²C standard
* _optional_ general purpose parallel IO port (<<_general_purpose_input_and_output_port_gpio,**GPIO**>>), 64xOut, 64xIn
* _optional_ 32-bit external bus interface, Wishbone b4 / AXI4-Lite compatible (<<_processor_external_bus_interface_xbus,**XBUS**>>)
* _optional_ watchdog timer (<<_watchdog_timer_wdt,**WDT**>>)
Expand Down Expand Up @@ -70,7 +71,7 @@ bits/channels are hardwired to zero.

.Tri-State Interfaces
[NOTE]
Some interfaces (like the TWI and the 1-Wire bus) require explicit tri-state drivers in the final top module.
Some interfaces (like the TWI, the TWD and the 1-Wire bus) require explicit tri-state drivers in the final top module.

.Input/Output Registers
[NOTE]
Expand Down Expand Up @@ -146,6 +147,11 @@ to all inputs and output so the synthesis tool can insert an explicit IO (bounda
| `twi_sda_o` | 1 | out | - | serial data line output (pull low only)
| `twi_scl_i` | 1 | in | `'H'` | serial clock line sense input
| `twi_scl_o` | 1 | out | - | serial clock line output (pull low only)
5+^| **<<_two_wire_serial_device_controller_twd>>**
| `twd_sda_i` | 1 | in | `'H'` | serial data line sense input
| `twd_sda_o` | 1 | out | - | serial data line output (pull low only)
| `twd_scl_i` | 1 | in | `'H'` | serial clock line sense input
| `twd_scl_o` | 1 | out | - | serial clock line output (pull low only)
5+^| **<<_one_wire_serial_interface_controller_onewire>>**
| `onewire_i` | 1 | in | `'H'` | 1-wire bus sense input
| `onewire_o` | 1 | out | - | 1-wire bus output (pull low only)
Expand Down Expand Up @@ -293,6 +299,8 @@ The generic type "`suv(x:y)`" is an abbreviation for "`std_ulogic_vector(x downt
| `IO_SDI_FIFO` | natural | 1 | Depth of the <<_serial_data_interface_controller_sdi>> FIFO. Has to be a power of two, min 1, max 32768.
| `IO_TWI_EN` | boolean | false | Implement the <<_two_wire_serial_interface_controller_twi>>.
| `IO_TWI_FIFO` | natural | 1 | Depth of the <<_two_wire_serial_interface_controller_twi>> FIFO. Has to be a power of two, min 1, max 32768.
| `IO_TWD_EN` | boolean | false | Implement the <<_two_wire_serial_device_controller_twd>>.
| `IO_TWD_FIFO` | natural | 1 | Depth of the <<_two_wire_serial_device_controller_twd>> FIFO. Has to be a power of two, min 1, max 32768.
| `IO_PWM_NUM_CH` | natural | 0 | Number of channels of the <<_pulse_width_modulation_controller_pwm>> to implement (0..16).
| `IO_WDT_EN` | boolean | false | Implement the <<_watchdog_timer_wdt>>.
| `IO_TRNG_EN` | boolean | false | Implement the <<_true_random_number_generator_trng>>.
Expand Down Expand Up @@ -449,7 +457,7 @@ table (the channel number also corresponds to the according FIRQ priority: 0 = h
[options="header",grid="rows"]
|=======================
| Channel | Source | Description
| 0 | _reserved_ | _hardwired to zero_
| 0 | <<_two_wire_serial_device_controller_twd,TWD>> | TWD FIFO level interrupt
| 1 | <<_custom_functions_subsystem_cfs,CFS>> | Custom functions subsystem (CFS) interrupt (user-defined)
| 2 | <<_primary_universal_asynchronous_receiver_and_transmitter_uart0,UART0>> | UART0 RX FIFO level interrupt
| 3 | <<_primary_universal_asynchronous_receiver_and_transmitter_uart0,UART0>> | UART0 TX FIFO level interrupt
Expand Down Expand Up @@ -810,6 +818,8 @@ include::soc_sdi.adoc[]

include::soc_twi.adoc[]

include::soc_twd.adoc[]

include::soc_onewire.adoc[]

include::soc_pwm.adoc[]
Expand Down
2 changes: 1 addition & 1 deletion docs/datasheet/soc_sysinfo.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Bit fields in this register are set to all-zero if the according memory system i
| `10` | `SYSINFO_SOC_XIP_CACHE` | set if XIP cache is implemented (via top's `XIP_CACHE_EN` generic)
| `11` | `SYSINFO_SOC_OCD_AUTH` | set if on-chip debugger authentication is implemented (via top's `OCD_AUTHENTICATION` generic)
| `12` | `SYSINFO_SOC_IMEM_ROM` | set if processor-internal IMEM is implemented as pre-initialized ROM (via top's `BOOT_MODE_SELECT` generic; see <<_boot_configuration>>)
| `13` | - | _reserved_, read as zero
| `13` | `SYSINFO_SOC_IO_TWD` | set if TWD is implemented (via top's `IO_TWD_EN` generic)
| `14` | `SYSINFO_SOC_IO_DMA` | set if direct memory access controller is implemented (via top's `IO_DMA_EN` generic)
| `15` | `SYSINFO_SOC_IO_GPIO` | set if GPIO is implemented (via top's `IO_GPIO_EN` generic)
| `16` | `SYSINFO_SOC_IO_MTIME` | set if MTIME is implemented (via top's `IO_MTIME_EN` generic)
Expand Down
167 changes: 167 additions & 0 deletions docs/datasheet/soc_twd.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<<<
:sectnums:
==== Two-Wire Serial Device Controller (TWD)

[cols="<3,<3,<4"]
[frame="topbot",grid="none"]
|=======================
| Hardware source files: | neorv32_twd.vhd |
| Software driver files: | neorv32_twd.c |
| | neorv32_twd.h |
| Top entity ports: | `twd_sda_i` | 1-bit serial data line sense input
| | `twd_sda_o` | 1-bit serial data line output (pull low only)
| | `twd_scl_i` | 1-bit serial clock line sense input
| | `twd_scl_o` | 1-bit serial clock line output (pull low only)
| Configuration generics: | `IO_TWD_EN` | implement TWD controller when `true`
| | `IO_TWD_FIFO` | RX/TX FIFO depth, has to be a power of two, min 1
| CPU interrupts: | fast IRQ channel 0 | FIFO status interrupt (see <<_processor_interrupts>>)
| Access restrictions: 2+| privileged access only, non-32-bit write accesses are ignored
|=======================


**Overview**

The NEORV32 TWD implements a I2C-compatible **device-mode** controller. Processor-external hosts can communicate
with this module by issuing I2C transactions. The TWD is entirely passive an only reacts on those transmissions.

Key features:

* Programmable 7-bit device address
* Programmable interrupt conditions
* Configurable RX/TX data FIFO to "program" large TWD sequences without further involvement of the CPU
.Device-Mode Only
[NOTE]
The NEORV32 TWD controller only supports **device mode**. Transmission are initiated by processor-external modules
and not by an external TWD. If you are looking for a _host-mode_ module (transactions initiated by the processor)
check out the <<_two_wire_serial_interface_controller_twi>>.


**Theory of Operation**

The TWD module provides two memory-mapped registers that are used for configuration & status check (`CTRL`) and
for accessing transmission data (`DATA`). The `DATA` register is transparently buffered by separate RX and TX FIFOs.
The size of those FIFOs can be configured by the `IO_TWD_FIFO` generic. Software can determine the FIFO size via the
`TWD_CTRL_FIFO_*` bits.

The module is globally enabled by setting the control register's `TWD_CTRL_EN` bit. Clearing this bit will disable
and reset the entire module also clearing the internal RX and TX FIFOs. Each FIFO can also be cleared individually at
any time by setting `TWD_CTRL_CLR_RX` or `TWD_CTRL_CLR_TX`, respectively.

The external two wire bus is sampled sampled and synchronized to processor's clock domain with a sampling frequency
of 1/8 of the processor's main clock. To increase the resistance to glitches the sampling frequency can be lowered
to 1/64 of the processor clock by setting the `TWD_CTRL_FSEL` bit.

.Current Bus State
[TIP]
The current state of the I²C bus lines (SCL and SDA) can be checked by software via the `TWD_CTRL_SENSE_*` control
register bits. Note that the TWD module needs to be enabled in order to sample the bus state.

The actual 7-bit device address of the TWD is programmed by the `TWD_CTRL_DEV_ADDR` bits. Note that the TWD will
only response to a host transactions if the host issues the according address. Specific general-call or broadcast
addresses are not supported.

Depending on the transaction type, data is either read from the RX FIFO and transferred to the host ("read operation")
or data is received from the host and written to the TX FIFO ("write operation"). Hence, data sequences can be
programmed to the TX FIFO to be fetched from the host. If the TX FIFO is empty and the host keeps performing read
transaction, the transferred data byte is automatically set to all-one.

The current status of the RX and TX FIFO can be polled by software via the `TWD_CTRL_RX_*` and `TWD_CTRL_TX_*`
flags.


**TWD Interrupt**

The TWD module provides a single interrupt to signal certain FIFO conditions to the CPU. The control register's
`TWD_CTRL_IRQ_*` bits are used to enabled individual interrupt conditions. Note that all enabled conditions are
logically OR-ed.

* `TWD_CTRL_IRQ_RX_AVAIL`: trigger interrupt if at least one data byte is available in the RX FIFO
* `TWD_CTRL_IRQ_RX_FULL`: trigger interrupt if the RX FIFO is completely full
* `TWD_CTRL_IRQ_TX_EMPTY`: trigger interrupt if the TX FIFO is empty
The interrupt remains active until all enabled interrupt-causing conditions are resolved.
The interrupt can only trigger if the module is actually enabled (`TWD_CTRL_EN` is set).


**TWD Transmissions**

Two standard I²C-compatible transaction types are supported: **read** operations and **write** operations. These
two operation types are illustrated in the following figure (note that the transactions are split across two lines
to improve readability).

.TWD single-byte read and write transaction timing (not to scale)
image::twd_sequences.png[]

Any new transaction starts with a **START** condition. Then, the host transmits the 7 bit device address MSB-first
(green signals `A6` to `A0`) plus a command bit. The command bit can be either **write** (pulling the SDA line low)
or **read** (leaving the SDA line high). If the transferred address matches the one programmed to to `TWD_CTRL_DEV_ADDR`
control register bits the TWD module will response with an **ACK** (acknowledge) by pulling the SDA bus line actively
low during the 9th SCL clock pulse. If there is no address match the TWD will not interfere with the bus and move back
to idle state.

For a **write transaction** (upper timing diagram) the host can now transfer an arbitrary number of bytes (blue signals
`D7` to `D0`, MSB-first) to the TWD module. Each byte is acknowledged by the TWD by pulling SDA low during the 9th SCL
clock pules (**ACK**). Each received data byte is pushed to the internal RX FIFO. Data will be lost if the FIFO overflows.
The transaction is terminated when the host issues a **STOP** condition.

For a **read transaction** (lower timing diagram) the cost keeps the SDA line at high state while sending the clock
pulse. The TWD will read a byte from the internal TX FIFO and will transmit it MSB-first to the host (blue signals `D7`
to `D0)`. During the 9th clock pulse the host has to acknowledged the transfer (**ACK**). If no ACK is received by the
TWD no data is taken from the TX FIFO and the same byte can be transmitted in the next data phase. If the TX FIFO becomes
empty while the host keeps reading data, all-one bytes are transmitted. The transaction is terminated when the host
issues a **STOP** condition.

A **repeated-START** condition can be issued at any time bringing the TWD back to the start of the address/command
transmission phase. The control register's `TWD_CTRL_BUSY` flag remains high while a bus transaction is in progress.

.Abort / Termination
[TIP]
An active or even stuck transmission can be terminated at any time by disabling the TWD module.
This will also clear the RX/TX FIFOs.


**Tristate Drivers**

The TWD module requires two tristate drivers (actually: open-drain drivers - signals can only be actively driven low) for
the SDA and SCL lines, which have to be implemented by the user in the setup's top module / IO ring. A generic VHDL example
is shown below (here, `sda_io` and `scl_io` are the actual TWD bus lines, which are of type `std_logic`).

.TWD VHDL Tristate Driver Example
[source,VHDL]
----
sda_io <= '0' when (twd_sda_o = '0') else 'Z'; -- drive
scl_io <= '0' when (twd_scl_o = '0') else 'Z'; -- drive
twd_sda_i <= std_ulogic(sda_io); -- sense
twd_scl_i <= std_ulogic(scl_io); -- sense
----


**Register Map**

.TWD register map (`struct NEORV32_TWD`)
[cols="<2,<1,<4,^1,<7"]
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
.18+<| `0xffffea00` .18+<| `CTRL` <|`0` `TWD_CTRL_EN` ^| r/w <| TWD enable, reset if cleared
<|`1` `TWD_CTRL_CLR_RX` ^| -/w <| Clear RX FIFO, flag auto-clears
<|`2` `TWD_CTRL_CLR_TX` ^| -/w <| Clear TX FIFO, flag auto-clears
<|`3` `TWD_CTRL_FSEL` ^| r/w <| Bus sample clock / filter select
<|`10:4` `TWD_CTRL_DEV_ADDR6 : TWD_CTRL_DEV_ADDR0` ^| r/w <| Device address (7-bit)
<|`11` `TWD_CTRL_IRQ_RX_AVAIL` ^| r/w <| IRQ if RX FIFO data available
<|`12` `TWD_CTRL_IRQ_RX_FULL` ^| r/w <| IRQ if RX FIFO full
<|`13` `TWD_CTRL_IRQ_TX_EMPTY` ^| r/w <| IRQ if TX FIFO empty
<|`14:9` - ^| r/- <| _reserved_, read as zero
<|`18:15` `TWD_CTRL_FIFO_MSB : TWD_CTRL_FIFO_LSB` ^| r/- <| FIFO depth; log2(`IO_TWD_FIFO`)
<|`24:12` - ^| r/- <| _reserved_, read as zero
<|`25` `TWD_CTRL_RX_AVAIL` ^| r/- <| RX FIFO data available
<|`26` `TWD_CTRL_RX_FULL` ^| r/- <| RX FIFO full
<|`27` `TWD_CTRL_TX_EMPTY` ^| r/- <| TX FIFO empty
<|`28` `TWD_CTRL_TX_FULL` ^| r/- <| TX FIFO full
<|`29` `TWD_CTRL_SENSE_SCL` ^| r/- <| current state of the SCL bus line
<|`30` `TWD_CTRL_SENSE_SDA` ^| r/- <| current state of the SDA bus line
<|`31` `TWD_CTRL_BUSY` ^| r/- <| bus engine is busy (transaction in progress)
.2+<| `0xffffea04` .2+<| `DATA` <|`7:0` `TWD_DATA_MSB : TWD_DATA_LSB` ^| r/w <| RX/TX data FIFO access
<|`31:8` - ^| r/- <| _reserved_, read as zero
|=======================
13 changes: 9 additions & 4 deletions docs/datasheet/soc_twi.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@

**Overview**

The NEORV32 TWI implements an I2C-compatible host controller to communicate with arbitrary I2C-devices.
The NEORV32 TWI implements a I²C-compatible host controller to communicate with arbitrary I2C-devices.
Note that peripheral-mode (controller acts as a device) and multi-controller mode are not supported yet.

.Host-Mode Only
[NOTE]
The NEORV32 TWI controller only supports **host mode**. Transmission are initiated by the processor's TWI controller
and not by an external I²C module. If you are looking for a _device-mode_ module (transactions
initiated by an external host) check out the <<_two_wire_serial_device_controller_twd>>.

Key features:

Expand All @@ -32,7 +37,7 @@ Key features:
* Generate START / repeated-START and STOP conditions
* Sending & receiving 8 data bits including ACK/NACK
* Generating a host-ACK (ACK send by the TWI controller)
* Configurable data/command FIFO to "program" large TWI sequences without further involvement of the CPU
* Configurable data/command FIFO to "program" large I²C sequences without further involvement of the CPU
The TWI controller provides two memory-mapped registers that are used for configuring the module and
for triggering operations: the control and status register `CTRL` and the command and data register `DCMD`.
Expand All @@ -42,7 +47,7 @@ for triggering operations: the control and status register `CTRL` and the comman

The TWI module requires two tristate drivers (actually: open-drain drivers - signals can only be actively driven low) for
the SDA and SCL lines, which have to be implemented by the user in the setup's top module / IO ring. A generic VHDL example
is shown below (here, `sda_io` and `scl_io` are the actual TWI bus lines, which are of type `std_logic`).
is shown below (here, `sda_io` and `scl_io` are the actual I²C bus lines, which are of type `std_logic`).

.TWI VHDL Tristate Driver Example
[source,VHDL]
Expand Down Expand Up @@ -111,7 +116,7 @@ that have not been executed yet) or of the TWI bus engine is still processing an
An active transmission can be terminated at any time by disabling the TWI module. This will also clear the data/command FIFO.

[TIP]
The current state of the TWI bus lines (SCL and SDA) can be checked by software via the `TWI_CTRL_SENSE_*` control register bits.
The current state of the I²C bus lines (SCL and SDA) can be checked by software via the `TWI_CTRL_SENSE_*` control register bits.

[NOTE]
When reading data from a device, an all-one byte (`0xFF`) has to be written to TWI data register `NEORV32_TWI.DATA`
Expand Down
Binary file modified docs/figures/neorv32_processor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/figures/twd_sequences.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 3d986cb

Please sign in to comment.