[uart] Revise DIF to address NCO Width
As NCO formula depends on the NCO CSR width, DIF and the document are
revised.
Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/uart/doc/_index.md b/hw/ip/uart/doc/_index.md
index c59a257..1da3d15 100644
--- a/hw/ip/uart/doc/_index.md
+++ b/hw/ip/uart/doc/_index.md
@@ -158,7 +158,13 @@
set using the equation below, where `f_pclk` is the system clock frequency
provided to the UART. and `f_baud` is the desired baud rate (in bits per second).
-$$ NCO = {{2^{20} * f\_{baud}} \over {f\_{pclk}}} $$
+$$ NCO = 16 \times {{2^{$bits(NCO)} \times f\_{baud}} \over {f\_{pclk}}} $$
+
+The formula above depends on the NCO CSR width.
+The logic creates a x16 tick when the NCO counter overflows.
+So, the computed baud rate from NCO value is below.
+
+$$ f\_{baud} = {{1 \over 16} \times {NCO \over {2^{$bits(NCO)}}} \times {f\_{pclk}}} $$
Note that the NCO result from the above formula can be a fraction but
the NCO register only accepts an integer value. This will create an
@@ -186,8 +192,8 @@
can be supported, however if it is too far off an integer then the
baud rate cannot be supported. This check is needed when
-$$ {{baud} < {{40 * f\_{pclk}} \over {2^{20}}}} \qquad OR \qquad
-{{f\_{pclk}} > {{{2^{20}} * {baud}} \over {40}}} $$
+$$ {{baud} < {{40 * f\_{pclk}} \over {2^{$bits(NCO)+4}}}} \qquad OR \qquad
+{{f\_{pclk}} > {{{2^{$bits(NCO)+4}} * {baud}} \over {40}}} $$
Using rounded frequencies and common baud rates, this implies that
care is needed for 9600 baud and below if the system clock is under
@@ -344,7 +350,7 @@
#define CLK_FIXED_FREQ_HZ (50ULL * 1000 * 1000)
void uart_init(unsigned int baud) {
- // nco = 2^20 * baud / fclk
+ // nco = 2^20 * baud / fclk. Assume NCO width is 16bit.
uint64_t uart_ctrl_nco = ((uint64_t)baud << 20) / CLK_FIXED_FREQ_HZ;
REG32(UART_CTRL(0)) =
((uart_ctrl_nco & UART_CTRL_NCO_MASK) << UART_CTRL_NCO_OFFSET) |
diff --git a/sw/device/lib/dif/dif_uart.c b/sw/device/lib/dif/dif_uart.c
index ddbf49b..fef2bcf 100644
--- a/sw/device/lib/dif/dif_uart.c
+++ b/sw/device/lib/dif/dif_uart.c
@@ -8,6 +8,7 @@
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/mmio.h"
+
#include "uart_regs.h" // Generated.
#define UART_INTR_STATE_MASK 0xffffffffu
@@ -105,8 +106,22 @@
return kDifUartConfigBadConfig;
}
- // Calculation formula: NCO = 2^20 * baud / fclk.
- uint64_t nco = ((uint64_t)config->baudrate << 20) / config->clk_freq_hz;
+ // Calculation formula: NCO = 16 * 2^nco_width * baud / fclk.
+
+ // Compute NCO register bit width
+ uint32_t nco_width = 0;
+
+ for (int i = 0; i < 32; i++) {
+ nco_width += (UART_CTRL_NCO_MASK >> i) & 1;
+ }
+
+ _Static_assert((UART_CTRL_NCO_MASK >> 28) == 0,
+ "NCO bit width exceeds 28 bits.");
+
+ // NCO creates 16x of baudrate. So, in addition to the nco_width,
+ // 2^4 should be multiplied.
+ uint64_t nco =
+ ((uint64_t)config->baudrate << (nco_width + 4)) / config->clk_freq_hz;
uint32_t nco_masked = nco & UART_CTRL_NCO_MASK;
// Requested baudrate is too high for the given clock frequency.
@@ -513,8 +528,7 @@
}
bitfield_field32_t field = {
- .mask = 0x1,
- .index = loopback ? UART_CTRL_LLPBK : UART_CTRL_SLPBK,
+ .mask = 0x1, .index = loopback ? UART_CTRL_LLPBK : UART_CTRL_SLPBK,
};
uint32_t reg = mmio_region_read32(uart->base_addr, UART_CTRL_REG_OFFSET);