diff --git a/docs/datasheet/soc_twd.adoc b/docs/datasheet/soc_twd.adoc new file mode 100644 index 000000000..dd74f858f --- /dev/null +++ b/docs/datasheet/soc_twd.adoc @@ -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 +|======================= diff --git a/docs/figures/twd_sequences.png b/docs/figures/twd_sequences.png new file mode 100644 index 000000000..7471b485b Binary files /dev/null and b/docs/figures/twd_sequences.png differ diff --git a/docs/sources/twd_sequences.json b/docs/sources/twd_sequences.json new file mode 100644 index 000000000..4f2a3705b --- /dev/null +++ b/docs/sources/twd_sequences.json @@ -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' + ] +}