Skip to content

Commit

Permalink
[TWI] add bus sensing logic (#1111)
Browse files Browse the repository at this point in the history
  • Loading branch information
stnolting authored Dec 1, 2024
2 parents 9521dec + 917ce42 commit 7992c9f
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 31 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 |
|:----:|:-------:|:--------|:------:|
| 01.12.2024 | 1.10.6.8 | add TWI bus sensing logic | [#1111](https://github.com/stnolting/neorv32/pull/1111) |
| 26.11.2024 | 1.10.6.7 | :bug: fix some HDL issues that caused problems when auto-converting to Verilog | [#1103](https://github.com/stnolting/neorv32/pull/1103) |
| 23.11.2024 | 1.10.6.6 | CPU control: large code edits and cleanups | [#1099](https://github.com/stnolting/neorv32/pull/1099) |
| 10.11.2024 | 1.10.6.5 | :warning: switch to [xPack](https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack) as default prebuilt RISC-V GCC toolchain (now using `riscv-none-elf-` as default gcc prefix) | [#1091](https://github.com/stnolting/neorv32/pull/1091) |
Expand Down
23 changes: 14 additions & 9 deletions docs/datasheet/soc_twi.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
The NEORV32 TWI implements an I2C-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.

The TWI controller provides two memory-mapped registers that are used for configuring the module and
for triggering operation: `CTRL` is the control and status register, `DCMD` is the command and data register.


Key features:

Expand All @@ -37,10 +34,13 @@ Key features:
* 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
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`.


**Tristate Drivers**

The TWI module requires two tristate drivers (actually: open-drain drivers; signals can only be actively driven low) for
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`).

Expand All @@ -56,8 +56,8 @@ twi_scl_i <= std_ulogic(scl_io); -- sense

**TWI Clock Speed**

The TWI clock frequency is programmed by two bit-fields in the device's control register `CTRL`: a 3-bit `TWI_CTRL_PRSCx`
clock prescaler is sued for a coarse clock configuration and a 4-bit clock divider `TWI_CTRL_CDIVx` is used for a fine
The TWI clock frequency is programmed by two bit-fields in the device's control register `CTRL`: a 3-bit clock prescaler
(`TWI_CTRL_PRSCx`) is used for a coarse clock configuration and a 4-bit clock divider (`TWI_CTRL_CDIVx`) is used for a fine
clock configuration.

.TWI prescaler configuration
Expand All @@ -74,7 +74,7 @@ from the processor's main clock f~main~ according to the following equation:
_**f~SCL~**_ = _f~main~[Hz]_ / (4 * `clock_prescaler` * (1 + TWI_CTRL_CDIV))

Hence, the maximum TWI clock is f~main~ / 8 and the lowest TWI clock is f~main~ / 262144. The generated TWI clock is
always symmetric having a duty cycle of exactly 50%.
always symmetric having a duty cycle of exactly 50% (if the clock is not haled by a device during clock stretching).

.Clock Stretching
[NOTE]
Expand Down Expand Up @@ -110,6 +110,9 @@ that have not been executed yet) or of the TWI bus engine is still processing an
[TIP]
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.

[NOTE]
When reading data from a device, an all-one byte (`0xFF`) has to be written to TWI data register `NEORV32_TWI.DATA`
so the accessed device can actively pull-down SDA when required.
Expand All @@ -128,13 +131,15 @@ TWI module is enabled (`TWI_CTRL_EN` = `1`) and the TX FIFO is empty and the TWI
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
.10+<| `0xfffff900` .10+<| `CTRL` <|`0` `TWI_CTRL_EN` ^| r/w <| TWI enable, reset if cleared
.12+<| `0xfffff900` .12+<| `CTRL` <|`0` `TWI_CTRL_EN` ^| r/w <| TWI enable, reset if cleared
<|`3:1` `TWI_CTRL_PRSC2 : TWI_CTRL_PRSC0` ^| r/w <| 3-bit clock prescaler select
<|`7:4` `TWI_CTRL_CDIV3 : TWI_CTRL_CDIV0` ^| r/w <| 4-bit clock divider
<|`8` `TWI_CTRL_CLKSTR` ^| r/w <| Enable (allow) clock stretching
<|`14:9` - ^| r/- <| _reserved_, read as zero
<|`18:15` `TWI_CTRL_FIFO_MSB : TWI_CTRL_FIFO_LSB` ^| r/- <| FIFO depth; log2(`IO_TWI_FIFO`)
<|`28:12` - ^| r/- <| _reserved_, read as zero
<|`26:12` - ^| r/- <| _reserved_, read as zero
<|`27` `TWI_CTRL_SENSE_SCL` ^| r/- <| current state of the SCL bus line
<|`28` `TWI_CTRL_SENSE_SDA` ^| r/- <| current state of the SDA bus line
<|`29` `TWI_CTRL_TX_FULL` ^| r/- <| set if the TWI bus is claimed by any controller
<|`30` `TWI_CTRL_RX_AVAIL` ^| r/- <| RX FIFO data available
<|`31` `TWI_CTRL_BUSY` ^| r/- <| TWI bus engine busy or TX FIFO not empty
Expand Down
2 changes: 1 addition & 1 deletion rtl/core/neorv32_package.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ package neorv32_package is

-- Architecture Constants -----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01100607"; -- hardware version
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01100608"; -- hardware version
constant archid_c : natural := 19; -- official RISC-V architecture ID
constant XLEN : natural := 32; -- native data path width

Expand Down
12 changes: 8 additions & 4 deletions rtl/core/neorv32_twi.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ architecture neorv32_twi_rtl of neorv32_twi is
constant ctrl_fifo_size0_c : natural := 15; -- r/-: log2(fifo size), bit 0 (lsb)
constant ctrl_fifo_size3_c : natural := 18; -- r/-: log2(fifo size), bit 3 (msb)
--
constant ctrl_sense_scl_c : natural := 27; -- r/-: current state of the SCL bus line
constant ctrl_sense_sda_c : natural := 28; -- r/-: current state of the SDA bus line
constant ctrl_tx_full_c : natural := 29; -- r/-: TX FIFO full
constant ctrl_rx_avail_c : natural := 30; -- r/-: RX FIFO data available
constant ctrl_busy_c : natural := 31; -- r/-: Set if TWI unit is busy
Expand Down Expand Up @@ -142,9 +144,11 @@ begin
--
bus_rsp_o.data(ctrl_fifo_size3_c downto ctrl_fifo_size0_c) <= std_ulogic_vector(to_unsigned(index_size_f(IO_TWI_FIFO), 4));
--
bus_rsp_o.data(ctrl_tx_full_c) <= not fifo.tx_free;
bus_rsp_o.data(ctrl_rx_avail_c) <= fifo.rx_avail;
bus_rsp_o.data(ctrl_busy_c) <= engine.busy or fifo.tx_avail; -- bus engine busy or TX FIFO not empty
bus_rsp_o.data(ctrl_sense_scl_c) <= io_con.scl_in_ff(1);
bus_rsp_o.data(ctrl_sense_sda_c) <= io_con.sda_in_ff(1);
bus_rsp_o.data(ctrl_tx_full_c) <= not fifo.tx_free;
bus_rsp_o.data(ctrl_rx_avail_c) <= fifo.rx_avail;
bus_rsp_o.data(ctrl_busy_c) <= engine.busy or fifo.tx_avail; -- bus engine busy or TX FIFO not empty
else -- RX FIFO
bus_rsp_o.data(8 downto 0) <= fifo.rx_rdata; -- ACK + data
end if;
Expand Down Expand Up @@ -378,7 +382,7 @@ begin
engine.state(1 downto 0) <= "00"; -- go back to IDLE
end if;

when others => -- "0---" OFFLINE: TWI deactivated, bus unclaimed
when others => -- "0--" OFFLINE: TWI deactivated, bus unclaimed
-- ------------------------------------------------------------
io_con.scl_out <= '1'; -- SCL driven by pull-up resistor
io_con.sda_out <= '1'; -- SDA driven by pull-up resistor
Expand Down
13 changes: 13 additions & 0 deletions sw/example/demo_twi/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ int main() {
" start - generate (repeated) START condition\n"
" stop - generate STOP condition\n"
" send - write/read single byte to/from bus\n"
" sense - show current SCL and SDA bus levels\n"
"Start a new transmission by generating a START condition. Next, transfer the 7-bit device address\n"
"and the R/W flag. After that, transfer your data to be written or send a 0xFF if you want to read\n"
"data from the bus. Finish the transmission by generating a STOP condition.\n");
Expand All @@ -110,6 +111,10 @@ int main() {
else if (!strcmp(buffer, "send")) {
send_twi();
}
else if (!strcmp(buffer, "sense")) {
neorv32_uart0_printf(" SCL: %u\n", neorv32_twi_sense_scl());
neorv32_uart0_printf(" SDA: %u\n", neorv32_twi_sense_sda());
}
else {
neorv32_uart0_printf("Invalid command. Type 'help' to see all commands.\n");
}
Expand Down Expand Up @@ -168,6 +173,14 @@ void set_clock(void) {
// print new clock frequency
uint32_t clock = neorv32_sysinfo_get_clk() / (4 * PRSC_LUT[prsc] * (1 + cdiv));
neorv32_uart0_printf("\nNew I2C clock: %u Hz\n", clock);

// check if bus lines are OK
if (neorv32_twi_sense_scl() != 1) {
neorv32_uart0_printf("WARNING! SCL bus line is not idle-high! Pull-up missing?\n");
}
if (neorv32_twi_sense_sda() != 1) {
neorv32_uart0_printf("WARNING! SDA bus line is not idle-high! Pull-up missing?\n");
}
}


Expand Down
38 changes: 22 additions & 16 deletions sw/lib/include/neorv32_twi.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,24 @@ typedef volatile struct __attribute__((packed,aligned(4))) {

/** TWI control register bits */
enum NEORV32_TWI_CTRL_enum {
TWI_CTRL_EN = 0, /**< TWI control register(0) (r/w): TWI enable */
TWI_CTRL_PRSC0 = 1, /**< TWI control register(1) (r/w): Clock prescaler select bit 0 */
TWI_CTRL_PRSC1 = 2, /**< TWI control register(2) (r/w): Clock prescaler select bit 1 */
TWI_CTRL_PRSC2 = 3, /**< TWI control register(3) (r/w): Clock prescaler select bit 2 */
TWI_CTRL_CDIV0 = 4, /**< TWI control register(4) (r/w): Clock divider bit 0 */
TWI_CTRL_CDIV1 = 5, /**< TWI control register(5) (r/w): Clock divider bit 1 */
TWI_CTRL_CDIV2 = 6, /**< TWI control register(6) (r/w): Clock divider bit 2 */
TWI_CTRL_CDIV3 = 7, /**< TWI control register(7) (r/w): Clock divider bit 3 */
TWI_CTRL_CLKSTR = 8, /**< TWI control register(8) (r/w): Enable/allow clock stretching */

TWI_CTRL_FIFO_LSB = 15, /**< SPI control register(15) (r/-): log2(FIFO size), lsb */
TWI_CTRL_FIFO_MSB = 18, /**< SPI control register(18) (r/-): log2(FIFO size), msb */

TWI_CTRL_TX_FULL = 29, /**< TWI control register(29) (r/-): TX FIFO full */
TWI_CTRL_RX_AVAIL = 30, /**< TWI control register(30) (r/-): RX FIFO data available */
TWI_CTRL_BUSY = 31 /**< TWI control register(31) (r/-): Bus engine busy or TX FIFO not empty */
TWI_CTRL_EN = 0, /**< TWI control register(0) (r/w): TWI enable */
TWI_CTRL_PRSC0 = 1, /**< TWI control register(1) (r/w): Clock prescaler select bit 0 */
TWI_CTRL_PRSC1 = 2, /**< TWI control register(2) (r/w): Clock prescaler select bit 1 */
TWI_CTRL_PRSC2 = 3, /**< TWI control register(3) (r/w): Clock prescaler select bit 2 */
TWI_CTRL_CDIV0 = 4, /**< TWI control register(4) (r/w): Clock divider bit 0 */
TWI_CTRL_CDIV1 = 5, /**< TWI control register(5) (r/w): Clock divider bit 1 */
TWI_CTRL_CDIV2 = 6, /**< TWI control register(6) (r/w): Clock divider bit 2 */
TWI_CTRL_CDIV3 = 7, /**< TWI control register(7) (r/w): Clock divider bit 3 */
TWI_CTRL_CLKSTR = 8, /**< TWI control register(8) (r/w): Enable/allow clock stretching */

TWI_CTRL_FIFO_LSB = 15, /**< SPI control register(15) (r/-): log2(FIFO size), lsb */
TWI_CTRL_FIFO_MSB = 18, /**< SPI control register(18) (r/-): log2(FIFO size), msb */

TWI_CTRL_SENSE_SCL = 27, /**< TWI control register(27) (r/-): current state of the SCL bus line */
TWI_CTRL_SENSE_SDA = 28, /**< TWI control register(28) (r/-): current state of the SDA bus line */
TWI_CTRL_TX_FULL = 29, /**< TWI control register(29) (r/-): TX FIFO full */
TWI_CTRL_RX_AVAIL = 30, /**< TWI control register(30) (r/-): RX FIFO data available */
TWI_CTRL_BUSY = 31 /**< TWI control register(31) (r/-): Bus engine busy or TX FIFO not empty */
};

/** TWI command/data register bits */
Expand All @@ -64,6 +66,7 @@ enum NEORV32_TWI_DCMD_enum {
};
/**@}*/


/**********************************************************************//**
* @name TWI commands
**************************************************************************/
Expand All @@ -85,6 +88,9 @@ int neorv32_twi_get_fifo_depth(void);
void neorv32_twi_disable(void);
void neorv32_twi_enable(void);

int neorv32_twi_sense_scl(void);
int neorv32_twi_sense_sda(void);

int neorv32_twi_busy(void);
int neorv32_twi_get(uint8_t *data);

Expand Down
34 changes: 33 additions & 1 deletion sw/lib/source/neorv32_twi.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,39 @@ void neorv32_twi_enable(void) {


/**********************************************************************//**
* Check if TWI is busy (TWI bus engine busy or TX FIFO not empty).
* Get current state of SCL bus line.
*
* @return 1 if SCL is high, 0 if SCL is low.
**************************************************************************/
int neorv32_twi_sense_scl(void) {

if (NEORV32_TWI->CTRL & (1 << TWI_CTRL_SENSE_SCL)) {
return 1;
}
else {
return 0;
}
}


/**********************************************************************//**
* Get current state of SDA bus line.
*
* @return 1 if SDA is high, 0 if SDA is low.
**************************************************************************/
int neorv32_twi_sense_sda(void) {

if (NEORV32_TWI->CTRL & (1 << TWI_CTRL_SENSE_SDA)) {
return 1;
}
else {
return 0;
}
}


/**********************************************************************//**
* Check if TWI controller is busy (TWI bus engine busy or TX FIFO not empty).
*
* @return 0 if idle, 1 if busy
**************************************************************************/
Expand Down
12 changes: 12 additions & 0 deletions sw/svd/neorv32.svd
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,18 @@
<access>read-only</access>
<description>TX FIFO full</description>
</field>
<field>
<name>TWI_CTRL_SENSE_SCL</name>
<bitRange>[27:27]</bitRange>
<access>read-only</access>
<description>current state of the SCL bus line</description>
</field>
<field>
<name>TWI_CTRL_SENSE_SDA</name>
<bitRange>[28:28]</bitRange>
<access>read-only</access>
<description>current state of the SDA bus line</description>
</field>
<field>
<name>TWI_CTRL_RX_AVAIL</name>
<bitRange>[30:30]</bitRange>
Expand Down

0 comments on commit 7992c9f

Please sign in to comment.