Skip to content

Commit

Permalink
[docs] add new TWD section
Browse files Browse the repository at this point in the history
  • Loading branch information
stnolting committed Dec 15, 2024
1 parent 69561dd commit 702e5ab
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
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
|=======================
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.
34 changes: 34 additions & 0 deletions docs/sources/twd_sequences.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{signal: [
[
"write byte",
{name: 'SDA', wave: '10.7..7..7..7..7..7..7..0..0..x|.', node: 'a.b.....................c..d..e', data: ['A6', 'A5', 'A4', 'A3', 'A2', 'A1', 'A0']},
{name: 'SCL', wave: '1.0.10.10.10.10.10.10.10.10.10.|.'},
{},
{name: 'SDA', wave: 'x|.5..5..5..5..5..5..5..5..0..0.1', node: '...........................f..gh.i', data: ['D7', 'D6', 'D5', 'D4', 'D3', 'D2', 'D1', 'D0']},
{name: 'SCL', wave: '0|..10.10.10.10.10.10.10.10.10.1.'}
],
{},
{},
[
"read byte",
{name: 'SDA', wave: '10.7..7..7..7..7..7..7..1..0..x|.', node: 'j.k.....................l..m..n', data: ['A6', 'A5', 'A4', 'A3', 'A2', 'A1', 'A0']},
{name: 'SCL', wave: '1.0.10.10.10.10.10.10.10.10.10.|.'},
{},
{name: 'SDA', wave: 'x|.9..9..9..9..9..9..9..9..0..0.1', node: '...........................o..pq.r', data: ['D7', 'D6', 'D5', 'D4', 'D3', 'D2', 'D1', 'D0']},
{name: 'SCL', wave: '0|..10.10.10.10.10.10.10.10.10.1.'}
]
],
edge: [
'a-b START',
'c-d WRITE',
'd-e ACK by TWD',
'f-g ACK by TWD',
'h-i STOP',

'j-k START',
'l-m READ',
'm-n ACK by TWD',
'o-p ACK by HOST',
'q-r STOP'
]
}

0 comments on commit 702e5ab

Please sign in to comment.