From 0858791d1c4c9e8183a5f2433b8e37fcffc72445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Thu, 14 Mar 2024 13:51:53 +0100 Subject: [PATCH 1/9] Add RFC for an UART peripheral. --- text/0000-soc-uart-peripheral.md | 329 ++++++++++++++++++ text/0000-soc-uart-peripheral/reg-divisor.svg | 1 + text/0000-soc-uart-peripheral/reg-enable.svg | 1 + text/0000-soc-uart-peripheral/reg-rx-data.svg | 1 + .../reg-rx-status.svg | 1 + text/0000-soc-uart-peripheral/reg-status.svg | 1 + text/0000-soc-uart-peripheral/reg-tx-data.svg | 1 + .../reg-tx-status.svg | 1 + 8 files changed, 336 insertions(+) create mode 100644 text/0000-soc-uart-peripheral.md create mode 100644 text/0000-soc-uart-peripheral/reg-divisor.svg create mode 100644 text/0000-soc-uart-peripheral/reg-enable.svg create mode 100644 text/0000-soc-uart-peripheral/reg-rx-data.svg create mode 100644 text/0000-soc-uart-peripheral/reg-rx-status.svg create mode 100644 text/0000-soc-uart-peripheral/reg-status.svg create mode 100644 text/0000-soc-uart-peripheral/reg-tx-data.svg create mode 100644 text/0000-soc-uart-peripheral/reg-tx-status.svg diff --git a/text/0000-soc-uart-peripheral.md b/text/0000-soc-uart-peripheral.md new file mode 100644 index 0000000..67199db --- /dev/null +++ b/text/0000-soc-uart-peripheral.md @@ -0,0 +1,329 @@ +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [amaranth-lang/rfcs#0000](https://github.com/amaranth-lang/rfcs/pull/0000) +- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) + +# UART peripheral RFC + +## Summary +[summary]: #summary + +Add a SoC peripheral for UART devices. + +## Motivation +[motivation]: #motivation + +An UART is a generally useful peripheral for serial communication between devices. + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +### Usage + +```python3 +from amaranth import * +from amaranth.lib import wiring +from amaranth.lib.wiring import connect + +from amaranth_stdio.serial import AsyncSerialRX, AsyncSerialTX + +from amaranth_soc import csr +from amaranth_soc import uart + + +class MySoC(wiring.Component): + def elaborate(self, platform): + m = Module() + + # ... + + # Instantiate an UART peripheral: + + uart_divisor = int(platform.default_clk_frequency / 115200) + + m.submodules.uart = uart = uart.Peripheral(divisor_init=uart_divisor, addr_width=8, data_width=8) + + # Instantiate and connect the UART PHYs: + + uart_pins = platform.request("uart", 0) + + uart_phy_rx = AsyncSerialRX(uart_divisor, divisor_bits=16, pins=uart_pins) + uart_phy_tx = AsyncSerialTX(uart_divisor, divisor_bits=16, pins=uart_pins) + + m.submodules.uart_phy_rx = uart_phy_rx + m.submodules.uart_phy_tx = uart_phy_tx + + m.d.comb += [ + uart_phy_rx.divisor.eq(uart.rx.divisor), + + uart.rx.stream.data.eq(uart_phy_rx.data), + uart.rx.stream.valid.eq(uart_phy_rx.rdy), + uart_phy_rx.ack.eq(uart.stream.ready), + + uart.rx.overflow.eq(uart_phy_rx.err.overflow), + uart.rx.error.eq(uart_phy_rx.err.frame), + ] + + m.d.comb += [ + uart_phy_tx.divisor.eq(uart.tx.divisor), + + uart_phy_tx.data.eq(uart.tx.stream.data), + uart_phy_tx.ack.eq(uart.tx.stream.valid), + uart.tx.stream.ready.eq(uart_phy_tx.rdy), + ] + + # Add the UART peripheral to a CSR bus decoder: + + m.submodules.csr_decoder = csr_decoder = csr.Decoder(addr_width=31, data_width=8) + + csr_decoder.add(uart.bus, addr=0x1000) + + # ... + + return m + +``` + +### Registers + +#### Receiver + +##### Enable (read/write) + +bf([
+         {name: 'en', bits: 1, attr: 'RW'},
+     ], {bits: 1}) + +- If `Enable.en` is 0, the receiver is held in reset state. + +- `Enable.en` is initialized to 0. + +##### Divisor (read/write) + +bf([
+         {name: 'cnt', bits: 13, attr: 'RW'},
+         {name: 'psc', bits: 3,  attr: 'RW'},
+     ], {bits: 16}) + +- If `Enable.en` is 0, `Divisor` is read-only. + +- `Divisor.cnt` is initialized to `divisor_cnt_init`. +- `Divisor.psc` is initialized to `divisor_psc_init`. + +Assuming a clock frequency of 100MHz, the receiver baudrate is computed like so: + +```python3 +baudrate = ((1 << psc) * 100e6) // (cnt + 1) +``` + +##### Status (read/write) + +bf([
+         { name: 'rdy', bits: 1, attr: 'R' },
+         { name: 'ovf', bits: 1, attr: 'RW1C' },
+         { name: 'err', bits: 1, attr: 'RW1C' },
+         { bits: 5, attr: 'ResR0W0' },
+     ], {bits: 8}) + +- `Status.rdy` indicates that the receive buffer contains at least one character. +- `Status.ovf` is set if a new frame was received while the receive buffer is full. +- `Status.err` is set if any implementation-specific error condition occured. + +- `Status.ovf` and `Status.err` are initialized to 0. + +##### Data (read-only) + +bf([
+         {name: 'data', bits: 8, attr: 'R'},
+     ], {bits: 8}) + +- If `Status.rdy` is 1, reading from `Data` consumes one character from the receive buffer. + +#### Transmitter + +##### Enable (read/write) + +bf([
+         {name: 'en', bits: 1, attr: 'RW'},
+     ], {bits: 1}) + +- If `Enable.en` is 0, the transmitter is held in reset state. + +- `Enable.en` is initialized to 0. + +##### Divisor (read/write) + +bf([
+         {name: 'cnt', bits: 13, attr: 'RW'},
+         {name: 'psc', bits: 3,  attr: 'RW'},
+     ], {bits: 16}) + +- If `Enable.en` is 0, `Divisor` is read-only. + +- `Divisor.cnt` is initialized to `divisor_cnt_init`. +- `Divisor.psc` is initialized to `divisor_psc_init`. + +Assuming a clock frequency of 100MHz, the transmitter baudrate is computed like so: + +```python3 +baudrate = ((2 ** psc) * 100e6) // (cnt + 1) +``` + +##### Status (read-only) + +bf([
+         { name: 'rdy', bits: 1, attr: 'R' },
+         { bits: 7, attr: 'ResR0W0' },
+     ], {bits: 8}) + +- `Status.rdy` indicates that the transmit buffer has available space for at least one character. + +##### Data (write-only) + +bf([
+         {name: 'data', bits: 8, attr: 'W'},
+     ], {bits: 8}) + +- If `Status.rdy` is 1, writing to `Data` adds one character to the transmit buffer. + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### `amaranth_soc.uart.ReceiverPHYSignature` + +The `uart.ReceiverPHYSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its receiver PHY, with: +- a `.__init__(self, *, data_bits)` constructor, where `data_bits` is a non-negative integer. + +Its members are defined as follows: + +```python3 +{ + "divisor": In(unsigned(20)), + "stream": Out(wiring.Signature({ + "data": Out(unsigned(data_bits)), + "valid": Out(unsigned(1)), + "ready": In(unsigned(1)), + })), + "overflow": Out(unsigned(1)), + "error": Out(unsigned(1)), +} +``` + +### `amaranth_soc.uart.TransmitterPHYSignature` + +The `uart.TransmitterSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its transmitter PHY, with: +- a `.__init__(self, *, data_bits)` constructor, where `data_bits` is a non-negative integer. + +Its members are defined as follows: + +```python3 +{ + "divisor": In(unsigned(20)), + "stream": In(wiring.Signature({ + "data": Out(unsigned(data_bits)), + "valid": Out(unsigned(1)), + "ready": In(unsigned(1)), + })), +} +``` + +### `amaranth_soc.uart.ReceiverPeripheral` + +The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: + * `divisor_init` is a positive integer used as initial value for `Divisor`. It is a 16-bit value, where the lower 13 bits are assigned to `Divisor.cnt`, and the upper 3 bits are assigned to `Divisor.psc` as a log2. + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. + * `data_bits` is a non-negative integer passed to `Data` and `ReceiverPHYSignature`. +- a `.signature` property, that returns a `wiring.Signature` with the following members: + +```python3 +{ + "bus": In(csr.Signature(addr_width, data_width)), + "phy": In(ReceiverPHYSignature(data_bits)), +} +``` + +### `amaranth_soc.uart.TransmitterPeripheral` + +The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the transmitter of an UART peripheral, with: +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: + * `divisor_init` is a positive integer used as initial value for `Divisor`. It is a 16-bit value, where the lower 13 bits are assigned to `Divisor.cnt`, and the upper 3 bits are assigned to `Divisor.psc` as a log2. + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. + * `data_bits` is a non-negative integer passed to `Data` and `TransmitterPHYSignature`. +- a `.signature` property, that returns a `wiring.Signature` with the following members: + +```python3 +{ + "bus": In(csr.Signature(addr_width, data_width)), + "phy": In(TransmitterPHYSignature(data_bits)), +} +``` + +### `amaranth_soc.uart.Peripheral` + +The `uart.Peripheral` class is a `wiring.Component` implementing an UART peripheral, with: +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: + * `divisor_init` is a positive integer used as initial value for `Divisor`. It is a 16-bit value, where the lower 13 bits are assigned to `Divisor.cnt`, and the upper 3 bits are assigned to `Divisor.psc` as a log2. + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. `addr_width` must be at least 1. The peripheral address space is split in two, with the lower half occupied by a `ReceiverPeripheral` and the upper by a `TransmitterPeripheral`. + * `data_bits` is a non-negative integer passed to `ReceiverPeripheral`, `TransmitterPeripheral`, `ReceiverPHYSignature` and `TransmitterPHYSignature`. + +- a `.signature` property, that returns a `wiring.Signature` with the following members: + +```python3 +{ + "bus": In(csr.Signature(addr_width, data_width)), + "rx": In(ReceiverPHYSignature(data_bits)), + "tx": In(TransmitterPHYSignature(data_bits)), +} +``` + +## Drawbacks +[drawbacks]: #drawbacks + +- This design decouples the UART peripheral from its PHY, which must be provided by the user. +- The receiver and transmitter have separate `Divider` registers, despite using identical values + in most cases. +- Configuring the baudrate through the `Divider` register requires knowledge of the clock frequency used by the peripheral. + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- This design is intended to be minimal and work reliably for the most common use-cases (i.e. 8-N-1). +- Decoupling the peripheral from the PHY allows flexibility in implementations. For example, it is easy to add FIFOs between the PHYs and the peripheral. +- A standalone `ReceiverPeripheral` or `TransmitterPeripheral` can be instantiated. + +- The choice of a 16-bit `Divisor` register with a 3-bit prescaler covers the most common frequency/baudrate combinations with an error rate (due to quantization) below 1%. + +*TODO: a table showing frequency/baudrate combinations* + +- As an alternative: + * implement the PHY in the peripheral itself, and expose pin interfaces in a similar manner as the GPIO peripheral of RFC 49. + +## Prior art +[prior-art]: #prior-art + +UART peripherals are commonly found in microcontrollers. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +None. + +## Future possibilities +[future-possibilities]: #future-possibilities + +- Add a separate 16550-compatible UART peripheral. +- Expand this peripheral with additional features, such as: + * parity + * auto baudrate + * oversampling + * hardware flow control + * interrupts + * DMA diff --git a/text/0000-soc-uart-peripheral/reg-divisor.svg b/text/0000-soc-uart-peripheral/reg-divisor.svg new file mode 100644 index 0000000..08a0002 --- /dev/null +++ b/text/0000-soc-uart-peripheral/reg-divisor.svg @@ -0,0 +1 @@ +0121315cntpscRWRW diff --git a/text/0000-soc-uart-peripheral/reg-enable.svg b/text/0000-soc-uart-peripheral/reg-enable.svg new file mode 100644 index 0000000..35da757 --- /dev/null +++ b/text/0000-soc-uart-peripheral/reg-enable.svg @@ -0,0 +1 @@ +0enRW diff --git a/text/0000-soc-uart-peripheral/reg-rx-data.svg b/text/0000-soc-uart-peripheral/reg-rx-data.svg new file mode 100644 index 0000000..a10cc15 --- /dev/null +++ b/text/0000-soc-uart-peripheral/reg-rx-data.svg @@ -0,0 +1 @@ +07dataR diff --git a/text/0000-soc-uart-peripheral/reg-rx-status.svg b/text/0000-soc-uart-peripheral/reg-rx-status.svg new file mode 100644 index 0000000..b813079 --- /dev/null +++ b/text/0000-soc-uart-peripheral/reg-rx-status.svg @@ -0,0 +1 @@ +01237rdyovferrRRW1CRW1CResR0W0 diff --git a/text/0000-soc-uart-peripheral/reg-status.svg b/text/0000-soc-uart-peripheral/reg-status.svg new file mode 100644 index 0000000..fd4ca80 --- /dev/null +++ b/text/0000-soc-uart-peripheral/reg-status.svg @@ -0,0 +1 @@ +023567rx_leveltx_levelrx_errorRRR diff --git a/text/0000-soc-uart-peripheral/reg-tx-data.svg b/text/0000-soc-uart-peripheral/reg-tx-data.svg new file mode 100644 index 0000000..8da0f43 --- /dev/null +++ b/text/0000-soc-uart-peripheral/reg-tx-data.svg @@ -0,0 +1 @@ +07dataW diff --git a/text/0000-soc-uart-peripheral/reg-tx-status.svg b/text/0000-soc-uart-peripheral/reg-tx-status.svg new file mode 100644 index 0000000..228ce96 --- /dev/null +++ b/text/0000-soc-uart-peripheral/reg-tx-status.svg @@ -0,0 +1 @@ +017rdyRResR0W0 \ No newline at end of file From ae049e6bfe96a5a7bacf3aab8727fd7c212675de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Wed, 20 Mar 2024 16:49:56 +0100 Subject: [PATCH 2/9] RFC #60: fix data stream signature and clock divisor. --- text/0000-soc-uart-peripheral.md | 62 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/text/0000-soc-uart-peripheral.md b/text/0000-soc-uart-peripheral.md index 67199db..92957b9 100644 --- a/text/0000-soc-uart-peripheral.md +++ b/text/0000-soc-uart-peripheral.md @@ -38,37 +38,37 @@ class MySoC(wiring.Component): # Instantiate an UART peripheral: - uart_divisor = int(platform.default_clk_frequency / 115200) + uart_divisor_cnt = int(platform.default_clk_frequency / 115200) - m.submodules.uart = uart = uart.Peripheral(divisor_init=uart_divisor, addr_width=8, data_width=8) + m.submodules.uart = uart = uart.Peripheral(divisor_init=(uart_divisor_cnt, 0), addr_width=8, data_width=8) # Instantiate and connect the UART PHYs: uart_pins = platform.request("uart", 0) - uart_phy_rx = AsyncSerialRX(uart_divisor, divisor_bits=16, pins=uart_pins) - uart_phy_tx = AsyncSerialTX(uart_divisor, divisor_bits=16, pins=uart_pins) + uart_phy_rx = AsyncSerialRX(uart_divisor_cnt, divisor_bits=16, pins=uart_pins) + uart_phy_tx = AsyncSerialTX(uart_divisor_cnt, divisor_bits=16, pins=uart_pins) m.submodules.uart_phy_rx = uart_phy_rx m.submodules.uart_phy_tx = uart_phy_tx m.d.comb += [ - uart_phy_rx.divisor.eq(uart.rx.divisor), + uart_phy_rx.divisor.eq(uart.rx.divisor.cnt), - uart.rx.stream.data.eq(uart_phy_rx.data), - uart.rx.stream.valid.eq(uart_phy_rx.rdy), - uart_phy_rx.ack.eq(uart.stream.ready), + uart.rx.data.payload.eq(uart_phy_rx.data), + uart.rx.data.valid.eq(uart_phy_rx.rdy), + uart_phy_rx.ack.eq(uart.rx.data.ready), uart.rx.overflow.eq(uart_phy_rx.err.overflow), uart.rx.error.eq(uart_phy_rx.err.frame), ] m.d.comb += [ - uart_phy_tx.divisor.eq(uart.tx.divisor), + uart_phy_tx.divisor.eq(uart.tx.divisor.cnt), - uart_phy_tx.data.eq(uart.tx.stream.data), - uart_phy_tx.ack.eq(uart.tx.stream.valid), - uart.tx.stream.ready.eq(uart_phy_tx.rdy), + uart_phy_tx.data.eq(uart.tx.data.payload), + uart_phy_tx.ack.eq(uart.tx.data.valid), + uart.tx.data.ready.eq(uart_phy_tx.rdy), ] # Add the UART peripheral to a CSR bus decoder: @@ -108,8 +108,8 @@ class MySoC(wiring.Component): - If `Enable.en` is 0, `Divisor` is read-only. -- `Divisor.cnt` is initialized to `divisor_cnt_init`. -- `Divisor.psc` is initialized to `divisor_psc_init`. +- `Divisor.cnt` is initialized to `divisor_init[0]`. +- `Divisor.psc` is initialized to `divisor_init[1]`. Assuming a clock frequency of 100MHz, the receiver baudrate is computed like so: @@ -165,8 +165,8 @@ baudrate = ((1 << psc) * 100e6) // (cnt + 1) - If `Enable.en` is 0, `Divisor` is read-only. -- `Divisor.cnt` is initialized to `divisor_cnt_init`. -- `Divisor.psc` is initialized to `divisor_psc_init`. +- `Divisor.cnt` is initialized to `divisor_init[0]`. +- `Divisor.psc` is initialized to `divisor_init[1]`. Assuming a clock frequency of 100MHz, the transmitter baudrate is computed like so: @@ -205,11 +205,11 @@ Its members are defined as follows: ```python3 { - "divisor": In(unsigned(20)), - "stream": Out(wiring.Signature({ - "data": Out(unsigned(data_bits)), - "valid": Out(unsigned(1)), - "ready": In(unsigned(1)), + "divisor": In(data.StructLayout({"cnt": unsigned(13), "psc": unsigned(3)})), + "data": Out(wiring.Signature({ + "payload": Out(unsigned(data_bits)), + "valid": Out(unsigned(1)), + "ready": In(unsigned(1)), })), "overflow": Out(unsigned(1)), "error": Out(unsigned(1)), @@ -225,11 +225,11 @@ Its members are defined as follows: ```python3 { - "divisor": In(unsigned(20)), - "stream": In(wiring.Signature({ - "data": Out(unsigned(data_bits)), - "valid": Out(unsigned(1)), - "ready": In(unsigned(1)), + "divisor": In(data.StructLayout({"cnt": unsigned(13), "psc": unsigned(3)})), + "data": In(wiring.Signature({ + "payload": Out(unsigned(data_bits)), + "valid": Out(unsigned(1)), + "ready": In(unsigned(1)), })), } ``` @@ -238,7 +238,7 @@ Its members are defined as follows: The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: - a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: - * `divisor_init` is a positive integer used as initial value for `Divisor`. It is a 16-bit value, where the lower 13 bits are assigned to `Divisor.cnt`, and the upper 3 bits are assigned to `Divisor.psc` as a log2. + * `divisor_init` is a tuple of two positive integers used as initial values for `Divisor.cnt` and `Divisor.psc` respectively. * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. * `data_bits` is a non-negative integer passed to `Data` and `ReceiverPHYSignature`. - a `.signature` property, that returns a `wiring.Signature` with the following members: @@ -254,7 +254,7 @@ The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the rec The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the transmitter of an UART peripheral, with: - a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: - * `divisor_init` is a positive integer used as initial value for `Divisor`. It is a 16-bit value, where the lower 13 bits are assigned to `Divisor.cnt`, and the upper 3 bits are assigned to `Divisor.psc` as a log2. + * `divisor_init` is a tuple of two positive integers used as initial values for `Divisor.cnt` and `Divisor.psc` respectively. * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. * `data_bits` is a non-negative integer passed to `Data` and `TransmitterPHYSignature`. - a `.signature` property, that returns a `wiring.Signature` with the following members: @@ -270,7 +270,7 @@ The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the The `uart.Peripheral` class is a `wiring.Component` implementing an UART peripheral, with: - a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: - * `divisor_init` is a positive integer used as initial value for `Divisor`. It is a 16-bit value, where the lower 13 bits are assigned to `Divisor.cnt`, and the upper 3 bits are assigned to `Divisor.psc` as a log2. + * `divisor_init` is a tuple of two positive integers used as initial values for `Divisor.cnt` and `Divisor.psc` respectively. * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. `addr_width` must be at least 1. The peripheral address space is split in two, with the lower half occupied by a `ReceiverPeripheral` and the upper by a `TransmitterPeripheral`. * `data_bits` is a non-negative integer passed to `ReceiverPeripheral`, `TransmitterPeripheral`, `ReceiverPHYSignature` and `TransmitterPHYSignature`. @@ -290,7 +290,7 @@ The `uart.Peripheral` class is a `wiring.Component` implementing an UART periphe - This design decouples the UART peripheral from its PHY, which must be provided by the user. - The receiver and transmitter have separate `Divider` registers, despite using identical values in most cases. -- Configuring the baudrate through the `Divider` register requires knowledge of the clock frequency used by the peripheral. +- Configuring the baudrate through the `Divisor` register requires knowledge of the clock frequency used by the peripheral. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -299,7 +299,7 @@ The `uart.Peripheral` class is a `wiring.Component` implementing an UART periphe - Decoupling the peripheral from the PHY allows flexibility in implementations. For example, it is easy to add FIFOs between the PHYs and the peripheral. - A standalone `ReceiverPeripheral` or `TransmitterPeripheral` can be instantiated. -- The choice of a 16-bit `Divisor` register with a 3-bit prescaler covers the most common frequency/baudrate combinations with an error rate (due to quantization) below 1%. +- The choice of a 13-bit divisor with a 3-bit prescaler covers the most common frequency/baudrate combinations with an error rate (due to quantization) below 1%. *TODO: a table showing frequency/baudrate combinations* From 5c324aa21afea84cd3e9c40068a2808bdd6ecd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Wed, 20 Mar 2024 16:57:57 +0100 Subject: [PATCH 3/9] RFC #60: remove white background of SVG diagrams. --- text/0000-soc-uart-peripheral/reg-divisor.svg | 2 +- text/0000-soc-uart-peripheral/reg-enable.svg | 2 +- text/0000-soc-uart-peripheral/reg-rx-data.svg | 2 +- text/0000-soc-uart-peripheral/reg-rx-status.svg | 2 +- text/0000-soc-uart-peripheral/reg-status.svg | 1 - text/0000-soc-uart-peripheral/reg-tx-data.svg | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 text/0000-soc-uart-peripheral/reg-status.svg diff --git a/text/0000-soc-uart-peripheral/reg-divisor.svg b/text/0000-soc-uart-peripheral/reg-divisor.svg index 08a0002..518edb0 100644 --- a/text/0000-soc-uart-peripheral/reg-divisor.svg +++ b/text/0000-soc-uart-peripheral/reg-divisor.svg @@ -1 +1 @@ -0121315cntpscRWRW +0121315cntpscRWRW diff --git a/text/0000-soc-uart-peripheral/reg-enable.svg b/text/0000-soc-uart-peripheral/reg-enable.svg index 35da757..279ac47 100644 --- a/text/0000-soc-uart-peripheral/reg-enable.svg +++ b/text/0000-soc-uart-peripheral/reg-enable.svg @@ -1 +1 @@ -0enRW +0enRW diff --git a/text/0000-soc-uart-peripheral/reg-rx-data.svg b/text/0000-soc-uart-peripheral/reg-rx-data.svg index a10cc15..a6c1cb6 100644 --- a/text/0000-soc-uart-peripheral/reg-rx-data.svg +++ b/text/0000-soc-uart-peripheral/reg-rx-data.svg @@ -1 +1 @@ -07dataR +07dataR diff --git a/text/0000-soc-uart-peripheral/reg-rx-status.svg b/text/0000-soc-uart-peripheral/reg-rx-status.svg index b813079..9ab76d1 100644 --- a/text/0000-soc-uart-peripheral/reg-rx-status.svg +++ b/text/0000-soc-uart-peripheral/reg-rx-status.svg @@ -1 +1 @@ -01237rdyovferrRRW1CRW1CResR0W0 +01237rdyovferrRRW1CRW1CResR0W0 diff --git a/text/0000-soc-uart-peripheral/reg-status.svg b/text/0000-soc-uart-peripheral/reg-status.svg deleted file mode 100644 index fd4ca80..0000000 --- a/text/0000-soc-uart-peripheral/reg-status.svg +++ /dev/null @@ -1 +0,0 @@ -023567rx_leveltx_levelrx_errorRRR diff --git a/text/0000-soc-uart-peripheral/reg-tx-data.svg b/text/0000-soc-uart-peripheral/reg-tx-data.svg index 8da0f43..e094a7e 100644 --- a/text/0000-soc-uart-peripheral/reg-tx-data.svg +++ b/text/0000-soc-uart-peripheral/reg-tx-data.svg @@ -1 +1 @@ -07dataW +07dataW From eb38d71528fe55d471016f1c43c8f1e2475a380d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Wed, 20 Mar 2024 19:15:28 +0100 Subject: [PATCH 4/9] RFC #60: fix SVG backgrounds. --- text/0000-soc-uart-peripheral/reg-divisor.svg | 2 +- text/0000-soc-uart-peripheral/reg-enable.svg | 2 +- text/0000-soc-uart-peripheral/reg-rx-data.svg | 2 +- text/0000-soc-uart-peripheral/reg-rx-status.svg | 2 +- text/0000-soc-uart-peripheral/reg-tx-data.svg | 2 +- text/0000-soc-uart-peripheral/reg-tx-status.svg | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/text/0000-soc-uart-peripheral/reg-divisor.svg b/text/0000-soc-uart-peripheral/reg-divisor.svg index 518edb0..cd3a8cb 100644 --- a/text/0000-soc-uart-peripheral/reg-divisor.svg +++ b/text/0000-soc-uart-peripheral/reg-divisor.svg @@ -1 +1 @@ -0121315cntpscRWRW +0121315cntpscRWRW diff --git a/text/0000-soc-uart-peripheral/reg-enable.svg b/text/0000-soc-uart-peripheral/reg-enable.svg index 279ac47..c17f073 100644 --- a/text/0000-soc-uart-peripheral/reg-enable.svg +++ b/text/0000-soc-uart-peripheral/reg-enable.svg @@ -1 +1 @@ -0enRW +0enRW diff --git a/text/0000-soc-uart-peripheral/reg-rx-data.svg b/text/0000-soc-uart-peripheral/reg-rx-data.svg index a6c1cb6..e678da8 100644 --- a/text/0000-soc-uart-peripheral/reg-rx-data.svg +++ b/text/0000-soc-uart-peripheral/reg-rx-data.svg @@ -1 +1 @@ -07dataR +07dataR diff --git a/text/0000-soc-uart-peripheral/reg-rx-status.svg b/text/0000-soc-uart-peripheral/reg-rx-status.svg index 9ab76d1..51c4b24 100644 --- a/text/0000-soc-uart-peripheral/reg-rx-status.svg +++ b/text/0000-soc-uart-peripheral/reg-rx-status.svg @@ -1 +1 @@ -01237rdyovferrRRW1CRW1CResR0W0 +01237rdyovferrRRW1CRW1CResR0W0 diff --git a/text/0000-soc-uart-peripheral/reg-tx-data.svg b/text/0000-soc-uart-peripheral/reg-tx-data.svg index e094a7e..248f9b3 100644 --- a/text/0000-soc-uart-peripheral/reg-tx-data.svg +++ b/text/0000-soc-uart-peripheral/reg-tx-data.svg @@ -1 +1 @@ -07dataW +07dataW diff --git a/text/0000-soc-uart-peripheral/reg-tx-status.svg b/text/0000-soc-uart-peripheral/reg-tx-status.svg index 228ce96..cf4ca1a 100644 --- a/text/0000-soc-uart-peripheral/reg-tx-status.svg +++ b/text/0000-soc-uart-peripheral/reg-tx-status.svg @@ -1 +1 @@ -017rdyRResR0W0 \ No newline at end of file +017rdyRResR0W0 From d2c1834b02dc53120281c19cf980a88402ca909f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 22 Mar 2024 16:00:57 +0100 Subject: [PATCH 5/9] RFC #60: address some feedback. - do not enforce a divisor encoding (besides being 16 bits). - fix signature direction (the peripheral drives the interface). - do not use abbreviated names (`rdy` is renamed to `ready`, etc). - rename `Enable` to `Control` and add ResR0W0 padding bits. - clarify the behavior of `Control.enable` values (0 and 1). - reorder registers (`Status` now follows `Control`). - rename `data_bits` to `symbol_width` and `data` to `symbol`. - `symbol_width` must be a positive integer. --- text/0000-soc-uart-peripheral.md | 329 ---------------- text/0000-soc-uart-peripheral/reg-enable.svg | 1 - text/0060-soc-uart-peripheral.md | 351 ++++++++++++++++++ text/0060-soc-uart-peripheral/reg-control.svg | 1 + .../reg-divisor.svg | 2 +- .../reg-rx-data.svg | 0 .../reg-rx-status.svg | 2 +- .../reg-tx-data.svg | 0 .../reg-tx-status.svg | 2 +- 9 files changed, 355 insertions(+), 333 deletions(-) delete mode 100644 text/0000-soc-uart-peripheral.md delete mode 100644 text/0000-soc-uart-peripheral/reg-enable.svg create mode 100644 text/0060-soc-uart-peripheral.md create mode 100644 text/0060-soc-uart-peripheral/reg-control.svg rename text/{0000-soc-uart-peripheral => 0060-soc-uart-peripheral}/reg-divisor.svg (64%) rename text/{0000-soc-uart-peripheral => 0060-soc-uart-peripheral}/reg-rx-data.svg (100%) rename text/{0000-soc-uart-peripheral => 0060-soc-uart-peripheral}/reg-rx-status.svg (71%) rename text/{0000-soc-uart-peripheral => 0060-soc-uart-peripheral}/reg-tx-data.svg (100%) rename text/{0000-soc-uart-peripheral => 0060-soc-uart-peripheral}/reg-tx-status.svg (82%) diff --git a/text/0000-soc-uart-peripheral.md b/text/0000-soc-uart-peripheral.md deleted file mode 100644 index 92957b9..0000000 --- a/text/0000-soc-uart-peripheral.md +++ /dev/null @@ -1,329 +0,0 @@ -- Start Date: (fill me in with today's date, YYYY-MM-DD) -- RFC PR: [amaranth-lang/rfcs#0000](https://github.com/amaranth-lang/rfcs/pull/0000) -- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) - -# UART peripheral RFC - -## Summary -[summary]: #summary - -Add a SoC peripheral for UART devices. - -## Motivation -[motivation]: #motivation - -An UART is a generally useful peripheral for serial communication between devices. - -## Guide-level explanation -[guide-level-explanation]: #guide-level-explanation - -### Usage - -```python3 -from amaranth import * -from amaranth.lib import wiring -from amaranth.lib.wiring import connect - -from amaranth_stdio.serial import AsyncSerialRX, AsyncSerialTX - -from amaranth_soc import csr -from amaranth_soc import uart - - -class MySoC(wiring.Component): - def elaborate(self, platform): - m = Module() - - # ... - - # Instantiate an UART peripheral: - - uart_divisor_cnt = int(platform.default_clk_frequency / 115200) - - m.submodules.uart = uart = uart.Peripheral(divisor_init=(uart_divisor_cnt, 0), addr_width=8, data_width=8) - - # Instantiate and connect the UART PHYs: - - uart_pins = platform.request("uart", 0) - - uart_phy_rx = AsyncSerialRX(uart_divisor_cnt, divisor_bits=16, pins=uart_pins) - uart_phy_tx = AsyncSerialTX(uart_divisor_cnt, divisor_bits=16, pins=uart_pins) - - m.submodules.uart_phy_rx = uart_phy_rx - m.submodules.uart_phy_tx = uart_phy_tx - - m.d.comb += [ - uart_phy_rx.divisor.eq(uart.rx.divisor.cnt), - - uart.rx.data.payload.eq(uart_phy_rx.data), - uart.rx.data.valid.eq(uart_phy_rx.rdy), - uart_phy_rx.ack.eq(uart.rx.data.ready), - - uart.rx.overflow.eq(uart_phy_rx.err.overflow), - uart.rx.error.eq(uart_phy_rx.err.frame), - ] - - m.d.comb += [ - uart_phy_tx.divisor.eq(uart.tx.divisor.cnt), - - uart_phy_tx.data.eq(uart.tx.data.payload), - uart_phy_tx.ack.eq(uart.tx.data.valid), - uart.tx.data.ready.eq(uart_phy_tx.rdy), - ] - - # Add the UART peripheral to a CSR bus decoder: - - m.submodules.csr_decoder = csr_decoder = csr.Decoder(addr_width=31, data_width=8) - - csr_decoder.add(uart.bus, addr=0x1000) - - # ... - - return m - -``` - -### Registers - -#### Receiver - -##### Enable (read/write) - -bf([
-         {name: 'en', bits: 1, attr: 'RW'},
-     ], {bits: 1}) - -- If `Enable.en` is 0, the receiver is held in reset state. - -- `Enable.en` is initialized to 0. - -##### Divisor (read/write) - -bf([
-         {name: 'cnt', bits: 13, attr: 'RW'},
-         {name: 'psc', bits: 3,  attr: 'RW'},
-     ], {bits: 16}) - -- If `Enable.en` is 0, `Divisor` is read-only. - -- `Divisor.cnt` is initialized to `divisor_init[0]`. -- `Divisor.psc` is initialized to `divisor_init[1]`. - -Assuming a clock frequency of 100MHz, the receiver baudrate is computed like so: - -```python3 -baudrate = ((1 << psc) * 100e6) // (cnt + 1) -``` - -##### Status (read/write) - -bf([
-         { name: 'rdy', bits: 1, attr: 'R' },
-         { name: 'ovf', bits: 1, attr: 'RW1C' },
-         { name: 'err', bits: 1, attr: 'RW1C' },
-         { bits: 5, attr: 'ResR0W0' },
-     ], {bits: 8}) - -- `Status.rdy` indicates that the receive buffer contains at least one character. -- `Status.ovf` is set if a new frame was received while the receive buffer is full. -- `Status.err` is set if any implementation-specific error condition occured. - -- `Status.ovf` and `Status.err` are initialized to 0. - -##### Data (read-only) - -bf([
-         {name: 'data', bits: 8, attr: 'R'},
-     ], {bits: 8}) - -- If `Status.rdy` is 1, reading from `Data` consumes one character from the receive buffer. - -#### Transmitter - -##### Enable (read/write) - -bf([
-         {name: 'en', bits: 1, attr: 'RW'},
-     ], {bits: 1}) - -- If `Enable.en` is 0, the transmitter is held in reset state. - -- `Enable.en` is initialized to 0. - -##### Divisor (read/write) - -bf([
-         {name: 'cnt', bits: 13, attr: 'RW'},
-         {name: 'psc', bits: 3,  attr: 'RW'},
-     ], {bits: 16}) - -- If `Enable.en` is 0, `Divisor` is read-only. - -- `Divisor.cnt` is initialized to `divisor_init[0]`. -- `Divisor.psc` is initialized to `divisor_init[1]`. - -Assuming a clock frequency of 100MHz, the transmitter baudrate is computed like so: - -```python3 -baudrate = ((2 ** psc) * 100e6) // (cnt + 1) -``` - -##### Status (read-only) - -bf([
-         { name: 'rdy', bits: 1, attr: 'R' },
-         { bits: 7, attr: 'ResR0W0' },
-     ], {bits: 8}) - -- `Status.rdy` indicates that the transmit buffer has available space for at least one character. - -##### Data (write-only) - -bf([
-         {name: 'data', bits: 8, attr: 'W'},
-     ], {bits: 8}) - -- If `Status.rdy` is 1, writing to `Data` adds one character to the transmit buffer. - -## Reference-level explanation -[reference-level-explanation]: #reference-level-explanation - -### `amaranth_soc.uart.ReceiverPHYSignature` - -The `uart.ReceiverPHYSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its receiver PHY, with: -- a `.__init__(self, *, data_bits)` constructor, where `data_bits` is a non-negative integer. - -Its members are defined as follows: - -```python3 -{ - "divisor": In(data.StructLayout({"cnt": unsigned(13), "psc": unsigned(3)})), - "data": Out(wiring.Signature({ - "payload": Out(unsigned(data_bits)), - "valid": Out(unsigned(1)), - "ready": In(unsigned(1)), - })), - "overflow": Out(unsigned(1)), - "error": Out(unsigned(1)), -} -``` - -### `amaranth_soc.uart.TransmitterPHYSignature` - -The `uart.TransmitterSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its transmitter PHY, with: -- a `.__init__(self, *, data_bits)` constructor, where `data_bits` is a non-negative integer. - -Its members are defined as follows: - -```python3 -{ - "divisor": In(data.StructLayout({"cnt": unsigned(13), "psc": unsigned(3)})), - "data": In(wiring.Signature({ - "payload": Out(unsigned(data_bits)), - "valid": Out(unsigned(1)), - "ready": In(unsigned(1)), - })), -} -``` - -### `amaranth_soc.uart.ReceiverPeripheral` - -The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: -- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: - * `divisor_init` is a tuple of two positive integers used as initial values for `Divisor.cnt` and `Divisor.psc` respectively. - * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. - * `data_bits` is a non-negative integer passed to `Data` and `ReceiverPHYSignature`. -- a `.signature` property, that returns a `wiring.Signature` with the following members: - -```python3 -{ - "bus": In(csr.Signature(addr_width, data_width)), - "phy": In(ReceiverPHYSignature(data_bits)), -} -``` - -### `amaranth_soc.uart.TransmitterPeripheral` - -The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the transmitter of an UART peripheral, with: -- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: - * `divisor_init` is a tuple of two positive integers used as initial values for `Divisor.cnt` and `Divisor.psc` respectively. - * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. - * `data_bits` is a non-negative integer passed to `Data` and `TransmitterPHYSignature`. -- a `.signature` property, that returns a `wiring.Signature` with the following members: - -```python3 -{ - "bus": In(csr.Signature(addr_width, data_width)), - "phy": In(TransmitterPHYSignature(data_bits)), -} -``` - -### `amaranth_soc.uart.Peripheral` - -The `uart.Peripheral` class is a `wiring.Component` implementing an UART peripheral, with: -- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: - * `divisor_init` is a tuple of two positive integers used as initial values for `Divisor.cnt` and `Divisor.psc` respectively. - * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. `addr_width` must be at least 1. The peripheral address space is split in two, with the lower half occupied by a `ReceiverPeripheral` and the upper by a `TransmitterPeripheral`. - * `data_bits` is a non-negative integer passed to `ReceiverPeripheral`, `TransmitterPeripheral`, `ReceiverPHYSignature` and `TransmitterPHYSignature`. - -- a `.signature` property, that returns a `wiring.Signature` with the following members: - -```python3 -{ - "bus": In(csr.Signature(addr_width, data_width)), - "rx": In(ReceiverPHYSignature(data_bits)), - "tx": In(TransmitterPHYSignature(data_bits)), -} -``` - -## Drawbacks -[drawbacks]: #drawbacks - -- This design decouples the UART peripheral from its PHY, which must be provided by the user. -- The receiver and transmitter have separate `Divider` registers, despite using identical values - in most cases. -- Configuring the baudrate through the `Divisor` register requires knowledge of the clock frequency used by the peripheral. - -## Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives - -- This design is intended to be minimal and work reliably for the most common use-cases (i.e. 8-N-1). -- Decoupling the peripheral from the PHY allows flexibility in implementations. For example, it is easy to add FIFOs between the PHYs and the peripheral. -- A standalone `ReceiverPeripheral` or `TransmitterPeripheral` can be instantiated. - -- The choice of a 13-bit divisor with a 3-bit prescaler covers the most common frequency/baudrate combinations with an error rate (due to quantization) below 1%. - -*TODO: a table showing frequency/baudrate combinations* - -- As an alternative: - * implement the PHY in the peripheral itself, and expose pin interfaces in a similar manner as the GPIO peripheral of RFC 49. - -## Prior art -[prior-art]: #prior-art - -UART peripherals are commonly found in microcontrollers. - -## Unresolved questions -[unresolved-questions]: #unresolved-questions - -None. - -## Future possibilities -[future-possibilities]: #future-possibilities - -- Add a separate 16550-compatible UART peripheral. -- Expand this peripheral with additional features, such as: - * parity - * auto baudrate - * oversampling - * hardware flow control - * interrupts - * DMA diff --git a/text/0000-soc-uart-peripheral/reg-enable.svg b/text/0000-soc-uart-peripheral/reg-enable.svg deleted file mode 100644 index c17f073..0000000 --- a/text/0000-soc-uart-peripheral/reg-enable.svg +++ /dev/null @@ -1 +0,0 @@ -0enRW diff --git a/text/0060-soc-uart-peripheral.md b/text/0060-soc-uart-peripheral.md new file mode 100644 index 0000000..799e4ab --- /dev/null +++ b/text/0060-soc-uart-peripheral.md @@ -0,0 +1,351 @@ +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [amaranth-lang/rfcs#60](https://github.com/amaranth-lang/rfcs/pull/60) +- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) + +# UART peripheral RFC + +## Summary +[summary]: #summary + +Add a SoC peripheral for UART devices. + +## Motivation +[motivation]: #motivation + +An UART is a generally useful peripheral for serial communication between devices. + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +### Usage + +```python3 +from amaranth import * +from amaranth.lib import wiring +from amaranth.lib.wiring import connect + +from amaranth_stdio.serial import AsyncSerialRX, AsyncSerialTX + +from amaranth_soc import csr +from amaranth_soc import uart + + +class MySoC(wiring.Component): + def elaborate(self, platform): + m = Module() + + # ... + + # Instantiate an UART peripheral: + + uart_divisor = int(platform.default_clk_frequency / 115200) + + m.submodules.uart = uart = uart.Peripheral(divisor_init=uart_divisor, addr_width=8, data_width=8) + + # Instantiate and connect the UART PHYs: + + uart_pins = platform.request("uart", 0) + + uart_phy_rx = AsyncSerialRX(uart_divisor, divisor_bits=16, pins=uart_pins) + uart_phy_tx = AsyncSerialTX(uart_divisor, divisor_bits=16, pins=uart_pins) + + m.submodules.uart_phy_rx = uart_phy_rx + m.submodules.uart_phy_tx = uart_phy_tx + + m.d.comb += [ + uart_phy_rx.divisor.eq(uart.rx.divisor), + + uart.rx.data.payload.eq(uart_phy_rx.data), + uart.rx.data.valid.eq(uart_phy_rx.rdy), + uart_phy_rx.ack.eq(uart.rx.data.ready), + + uart.rx.overflow.eq(uart_phy_rx.err.overflow), + uart.rx.error.eq(uart_phy_rx.err.frame), + ] + + m.d.comb += [ + uart_phy_tx.divisor.eq(uart.tx.divisor), + + uart_phy_tx.data.eq(uart.tx.data.payload), + uart_phy_tx.ack.eq(uart.tx.data.valid), + uart.tx.data.ready.eq(uart_phy_tx.rdy), + ] + + # Add the UART peripheral to a CSR bus decoder: + + m.submodules.csr_decoder = csr_decoder = csr.Decoder(addr_width=31, data_width=8) + + csr_decoder.add(uart.bus, addr=0x1000) + + # ... + + return m + +``` + +### Registers + +#### Receiver + +##### Control (read/write) + +bf([
+         { name: 'enable', bits: 1, attr: 'RW' },
+         { bits: 7, attr: 'ResR0W0' },
+     ], {bits: 8}) + +`Control.enable` is initialized to 0 on reset. + +If `Control.enable` is 0, the receiver PHY should be held in reset state. +If `Control.enable` is 1, the receiver PHY should operate normally. + +##### Status (read/write) + +bf([
+         { name: 'ready',    bits: 1, attr: 'R' },
+         { name: 'overflow', bits: 1, attr: 'RW1C' },
+         { name: 'error',    bits: 1, attr: 'RW1C' },
+         { bits: 5, attr: 'ResR0W0' },
+     ], {bits: 8}) + +- `Status.ready` indicates that the receive buffer contains at least one character. +- `Status.overflow` is set and latched if a new frame was received while the receive buffer is full. +- `Status.error` is set and latched if any implementation-specific error condition occured. + +`Status.overflow` and `Status.error` are initialized to 0 on reset. + +The value of the `Status` register is not affected by `Control.enable`. + +##### Divisor (read/write) + +bf([
+         {name: 'divisor', bits: 16, attr: 'RW'},
+     ], {bits: 16}) + +The `Divisor` register exposes an implementation-specific mechanism to control the receiver baudrate. + +For example, assuming a clock frequency of 100MHz, an implementation may configure its baudrate like so: + +```python3 +baudrate = int(100e6 / divisor) +``` + +An implementation may also choose to ignore the `Divisor` register and configure the baudrate through unspecified means. + +If `Control.enable` is 0, `Divisor` is read/write. +If `Control.enable` is 1, `Divisor` is read-only. + +`Divisor` is initialized to `divisor_init` on reset. + +##### Data (read-only) + +bf([
+         {name: 'data', bits: 8, attr: 'R'},
+     ], {bits: 8}) + +If `Control.enable` is 0 or `Status.ready` is 0: + * reading from `Data` has no side-effect and returns an unspecified value. +If `Control.enable` is 1 and `Status.ready` is 1: + * reading from `Data` consumes one symbol from the receive buffer and returns it. + +#### Transmitter + +##### Control (read/write) + +bf([
+         { name: 'enable', bits: 1, attr: 'RW' },
+         { bits: 7, attr: 'ResR0W0' },
+     ], {bits: 1}) + +`Control.enable` is initialized to 0 on reset. + +If `Control.enable` is 0, the transmitter PHY should be held in reset state. +If `Control.enable` is 1, the transmitter PHY should operate normally. + +##### Status (read-only) + +bf([
+         { name: 'ready', bits: 1, attr: 'R' },
+         { bits: 7, attr: 'ResR0W0' },
+     ], {bits: 8}) + +- `Status.ready` indicates that the transmit buffer has available space for at least one character. + +##### Divisor (read/write) + +bf([
+         {name: 'divisor', bits: 16, attr: 'RW'},
+     ], {bits: 16}) + +The `Divisor` register exposes an implementation-specific mechanism to control the transmitter baudrate. + +For example, assuming a clock frequency of 100MHz, an implementation may configure its baudrate like so: + +```python3 +baudrate = int(100e6 / divisor) +``` + +An implementation may also choose to ignore the `Divisor` register and configure the baudrate through unspecified means. + +If `Control.enable` is 0, `Divisor` is read/write. +If `Control.enable` is 1, `Divisor` is read-only. + +`Divisor` is initialized to `divisor_init` on reset. + +##### Data (write-only) + +bf([
+         {name: 'data', bits: 8, attr: 'W'},
+     ], {bits: 8}) + +- If `Status.rdy` is 1, writing to `Data` adds one character to the transmit buffer. + +If `Control.enable` is 0 or `Status.ready` is 0: + * writing to `Data` has no side-effect. +If `Control.enable` is 1 and `Status.ready` is 1: + * writing to `Data` adds one symbol to the transmit buffer. + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### `amaranth_soc.uart.ReceiverPHYSignature` + +The `uart.ReceiverPHYSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its receiver PHY, with: +- a `.__init__(self, *, symbol_width)` constructor, where `symbol_width` is a positive integer. + +Its members are defined as follows: + +```python3 +{ + "divisor": Out(unsigned(16)), + "symbol": In(wiring.Signature({ + "payload": Out(unsigned(symbol_width)), + "valid": Out(1), + "ready": In(1), + })), + "overflow": In(1), + "error": In(1), +} +``` + +### `amaranth_soc.uart.TransmitterPHYSignature` + +The `uart.TransmitterSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its transmitter PHY, with: +- a `.__init__(self, *, symbol_width)` constructor, where `symbol_width` is a positive integer. + +Its members are defined as follows: + +```python3 +{ + "divisor": Out(unsigned(16)), + "symbol": Out(wiring.Signature({ + "payload": Out(unsigned(symbol_width)), + "valid": Out(unsigned(1)), + "ready": In(unsigned(1)), + })), +} +``` + +### `amaranth_soc.uart.ReceiverPeripheral` + +The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, symbol_width=8)` constructor, where: + * `divisor_init` is a positive integer used as initial values for `Divisor.divisor`. + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. + * `symbol_width` is a positive integer passed to `Data` and `ReceiverPHYSignature`. +- a `.signature` property, that returns a `wiring.Signature` with the following members: + +```python3 +{ + "bus": In(csr.Signature(addr_width, data_width)), + "phy": Out(ReceiverPHYSignature(symbol_width)), +} +``` + +### `amaranth_soc.uart.TransmitterPeripheral` + +The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the transmitter of an UART peripheral, with: +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, symbol_width=8)` constructor, where: + * `divisor_init` is a positive integer used as initial values for `Divisor.divisor`. + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. + * `symbol_width` is a positive integer passed to `Data` and `TransmitterPHYSignature`. +- a `.signature` property, that returns a `wiring.Signature` with the following members: + +```python3 +{ + "bus": In(csr.Signature(addr_width, data_width)), + "phy": Out(TransmitterPHYSignature(symbol_width)), +} +``` + +### `amaranth_soc.uart.Peripheral` + +The `uart.Peripheral` class is a `wiring.Component` implementing an UART peripheral, with: +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, symbol_width=8)` constructor, where: + * `divisor_init` is a positive integer used as initial values for `Divisor.divisor`. + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. `addr_width` must be at least 1. The peripheral address space is split in two, with the lower half occupied by a `ReceiverPeripheral` and the upper by a `TransmitterPeripheral`. + * `symbol_width` is a positive integer passed to `ReceiverPeripheral`, `TransmitterPeripheral`, `ReceiverPHYSignature` and `TransmitterPHYSignature`. + +- a `.signature` property, that returns a `wiring.Signature` with the following members: + +```python3 +{ + "bus": In(csr.Signature(addr_width, data_width)), + "rx": Out(ReceiverPHYSignature(symbol_width)), + "tx": Out(TransmitterPHYSignature(symbol_width)), +} +``` + +## Drawbacks +[drawbacks]: #drawbacks + +- This design decouples the UART peripheral from its PHY, which must be provided by the user. +- The receiver and transmitter have separate `Divider` registers, despite using identical values + in most cases. +- Configuring the baudrate through the `Divisor` register requires knowledge of the clock frequency used by the peripheral. + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- This design is intended to be minimal yet useful for the most common use-cases (i.e. 8-N-1). +- Decoupling the peripheral from the PHY allows flexibility in implementations. For example, it is easy to add FIFOs between the PHYs and the peripheral. +- A standalone `ReceiverPeripheral` or `TransmitterPeripheral` can be instantiated. + +- The choice of a 16-bit divisor with an (otherwise) unspecified encoding gives allows implementation freedom: + * some may not care about a clock divisor at all (e.g. a behavioral model of an UART PHY, interfacing with a pseudo-TTY). + * some may provide their own divisor encoding scheme (e.g. a 13-bit base value with a 3-bit scale, that can cover common frequency/baudrate combinations with a [<1% error rate](https://github.com/amaranth-lang/rfcs/files/14672989/baud.py.txt) (credit: [@whitequark](https://github.com/whitequark))). + + +- As an alternative: + * implement the PHY in the peripheral itself, and expose pin interfaces in a similar manner as the GPIO peripheral of [RFC 49](https://amaranth-lang.org/rfcs/0049-soc-gpio-peripheral.html). + +## Prior art +[prior-art]: #prior-art + +UART peripherals are commonly found in microcontrollers. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +None. + +## Future possibilities +[future-possibilities]: #future-possibilities + +- Add a separate 16550-compatible UART peripheral. +- Add a separate peripheral with additional features, such as: + * parity + * auto baudrate + * oversampling + * hardware flow control + * interrupts + * DMA +- Add support for interrupts to this peripheral. diff --git a/text/0060-soc-uart-peripheral/reg-control.svg b/text/0060-soc-uart-peripheral/reg-control.svg new file mode 100644 index 0000000..f54bf33 --- /dev/null +++ b/text/0060-soc-uart-peripheral/reg-control.svg @@ -0,0 +1 @@ +017enableRWResR0W0 diff --git a/text/0000-soc-uart-peripheral/reg-divisor.svg b/text/0060-soc-uart-peripheral/reg-divisor.svg similarity index 64% rename from text/0000-soc-uart-peripheral/reg-divisor.svg rename to text/0060-soc-uart-peripheral/reg-divisor.svg index cd3a8cb..7e96cfe 100644 --- a/text/0000-soc-uart-peripheral/reg-divisor.svg +++ b/text/0060-soc-uart-peripheral/reg-divisor.svg @@ -1 +1 @@ -0121315cntpscRWRW +015divisorRW diff --git a/text/0000-soc-uart-peripheral/reg-rx-data.svg b/text/0060-soc-uart-peripheral/reg-rx-data.svg similarity index 100% rename from text/0000-soc-uart-peripheral/reg-rx-data.svg rename to text/0060-soc-uart-peripheral/reg-rx-data.svg diff --git a/text/0000-soc-uart-peripheral/reg-rx-status.svg b/text/0060-soc-uart-peripheral/reg-rx-status.svg similarity index 71% rename from text/0000-soc-uart-peripheral/reg-rx-status.svg rename to text/0060-soc-uart-peripheral/reg-rx-status.svg index 51c4b24..6ee6136 100644 --- a/text/0000-soc-uart-peripheral/reg-rx-status.svg +++ b/text/0060-soc-uart-peripheral/reg-rx-status.svg @@ -1 +1 @@ -01237rdyovferrRRW1CRW1CResR0W0 +01237readyoverflowerrorRRW1CRW1CResR0W0 diff --git a/text/0000-soc-uart-peripheral/reg-tx-data.svg b/text/0060-soc-uart-peripheral/reg-tx-data.svg similarity index 100% rename from text/0000-soc-uart-peripheral/reg-tx-data.svg rename to text/0060-soc-uart-peripheral/reg-tx-data.svg diff --git a/text/0000-soc-uart-peripheral/reg-tx-status.svg b/text/0060-soc-uart-peripheral/reg-tx-status.svg similarity index 82% rename from text/0000-soc-uart-peripheral/reg-tx-status.svg rename to text/0060-soc-uart-peripheral/reg-tx-status.svg index cf4ca1a..545661d 100644 --- a/text/0000-soc-uart-peripheral/reg-tx-status.svg +++ b/text/0060-soc-uart-peripheral/reg-tx-status.svg @@ -1 +1 @@ -017rdyRResR0W0 +017readyRResR0W0 From 705e30762de45465f58f917b22b7ae03b01078ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 22 Mar 2024 16:12:31 +0100 Subject: [PATCH 6/9] RFC #60: fix formatting. --- text/0060-soc-uart-peripheral.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/text/0060-soc-uart-peripheral.md b/text/0060-soc-uart-peripheral.md index 799e4ab..1a8df40 100644 --- a/text/0060-soc-uart-peripheral.md +++ b/text/0060-soc-uart-peripheral.md @@ -97,8 +97,8 @@ class MySoC(wiring.Component): `Control.enable` is initialized to 0 on reset. -If `Control.enable` is 0, the receiver PHY should be held in reset state. -If `Control.enable` is 1, the receiver PHY should operate normally. +- If `Control.enable` is 0, the receiver PHY should be held in reset state. +- If `Control.enable` is 1, the receiver PHY should operate normally. ##### Status (read/write) @@ -135,8 +135,8 @@ baudrate = int(100e6 / divisor) An implementation may also choose to ignore the `Divisor` register and configure the baudrate through unspecified means. -If `Control.enable` is 0, `Divisor` is read/write. -If `Control.enable` is 1, `Divisor` is read-only. +- If `Control.enable` is 0, `Divisor` is read/write. +- If `Control.enable` is 1, `Divisor` is read-only. `Divisor` is initialized to `divisor_init` on reset. @@ -147,9 +147,9 @@ If `Control.enable` is 1, `Divisor` is read-only. {name: 'data', bits: 8, attr: 'R'}, ], {bits: 8})"> -If `Control.enable` is 0 or `Status.ready` is 0: +- If `Control.enable` is 0 or `Status.ready` is 0: * reading from `Data` has no side-effect and returns an unspecified value. -If `Control.enable` is 1 and `Status.ready` is 1: +- If `Control.enable` is 1 and `Status.ready` is 1: * reading from `Data` consumes one symbol from the receive buffer and returns it. #### Transmitter @@ -164,8 +164,8 @@ If `Control.enable` is 1 and `Status.ready` is 1: `Control.enable` is initialized to 0 on reset. -If `Control.enable` is 0, the transmitter PHY should be held in reset state. -If `Control.enable` is 1, the transmitter PHY should operate normally. +- If `Control.enable` is 0, the transmitter PHY should be held in reset state. +- If `Control.enable` is 1, the transmitter PHY should operate normally. ##### Status (read-only) @@ -194,8 +194,8 @@ baudrate = int(100e6 / divisor) An implementation may also choose to ignore the `Divisor` register and configure the baudrate through unspecified means. -If `Control.enable` is 0, `Divisor` is read/write. -If `Control.enable` is 1, `Divisor` is read-only. +- If `Control.enable` is 0, `Divisor` is read/write. +- If `Control.enable` is 1, `Divisor` is read-only. `Divisor` is initialized to `divisor_init` on reset. @@ -208,9 +208,9 @@ If `Control.enable` is 1, `Divisor` is read-only. - If `Status.rdy` is 1, writing to `Data` adds one character to the transmit buffer. -If `Control.enable` is 0 or `Status.ready` is 0: +- If `Control.enable` is 0 or `Status.ready` is 0: * writing to `Data` has no side-effect. -If `Control.enable` is 1 and `Status.ready` is 1: +- If `Control.enable` is 1 and `Status.ready` is 1: * writing to `Data` adds one symbol to the transmit buffer. ## Reference-level explanation From 5638b9d734b24e0a5a1844caa4886b4d0fec8f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 22 Mar 2024 16:36:29 +0100 Subject: [PATCH 7/9] RFC #60: add a `rst` member to the peripheral signature. --- text/0060-soc-uart-peripheral.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/text/0060-soc-uart-peripheral.md b/text/0060-soc-uart-peripheral.md index 1a8df40..77034e4 100644 --- a/text/0060-soc-uart-peripheral.md +++ b/text/0060-soc-uart-peripheral.md @@ -49,8 +49,8 @@ class MySoC(wiring.Component): uart_phy_rx = AsyncSerialRX(uart_divisor, divisor_bits=16, pins=uart_pins) uart_phy_tx = AsyncSerialTX(uart_divisor, divisor_bits=16, pins=uart_pins) - m.submodules.uart_phy_rx = uart_phy_rx - m.submodules.uart_phy_tx = uart_phy_tx + m.submodules.uart_phy_rx = ResetInserter(uart.rx.rst)(uart_phy_rx) + m.submodules.uart_phy_tx = ResetInserter(uart.tx.rst)(uart_phy_tx) m.d.comb += [ uart_phy_rx.divisor.eq(uart.rx.divisor), @@ -225,6 +225,7 @@ Its members are defined as follows: ```python3 { + "rst": Out(1), "divisor": Out(unsigned(16)), "symbol": In(wiring.Signature({ "payload": Out(unsigned(symbol_width)), @@ -236,6 +237,8 @@ Its members are defined as follows: } ``` +The `rst` port is driven to 1 if `Control.enable` is 0, and 0 if `Control.enable` is 1. + ### `amaranth_soc.uart.TransmitterPHYSignature` The `uart.TransmitterSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its transmitter PHY, with: @@ -245,15 +248,18 @@ Its members are defined as follows: ```python3 { + "rst": Out(1), "divisor": Out(unsigned(16)), "symbol": Out(wiring.Signature({ "payload": Out(unsigned(symbol_width)), - "valid": Out(unsigned(1)), - "ready": In(unsigned(1)), + "valid": Out(1), + "ready": In(1), })), } ``` +The `rst` port is driven to 1 if `Control.enable` is 0, and 0 if `Control.enable` is 1. + ### `amaranth_soc.uart.ReceiverPeripheral` The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: @@ -319,7 +325,7 @@ The `uart.Peripheral` class is a `wiring.Component` implementing an UART periphe - Decoupling the peripheral from the PHY allows flexibility in implementations. For example, it is easy to add FIFOs between the PHYs and the peripheral. - A standalone `ReceiverPeripheral` or `TransmitterPeripheral` can be instantiated. -- The choice of a 16-bit divisor with an (otherwise) unspecified encoding gives allows implementation freedom: +- The choice of a 16-bit divisor with an (otherwise) unspecified encoding allows implementation freedom: * some may not care about a clock divisor at all (e.g. a behavioral model of an UART PHY, interfacing with a pseudo-TTY). * some may provide their own divisor encoding scheme (e.g. a 13-bit base value with a 3-bit scale, that can cover common frequency/baudrate combinations with a [<1% error rate](https://github.com/amaranth-lang/rfcs/files/14672989/baud.py.txt) (credit: [@whitequark](https://github.com/whitequark))). From 64a9e12d4cb508a5e12f737ccf83108c089d61f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 22 Mar 2024 17:51:26 +0100 Subject: [PATCH 8/9] RFC #60: rename `symbol` to `symbols` and `bus` to `csr_bus`. --- text/0060-soc-uart-peripheral.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/text/0060-soc-uart-peripheral.md b/text/0060-soc-uart-peripheral.md index 77034e4..2066b26 100644 --- a/text/0060-soc-uart-peripheral.md +++ b/text/0060-soc-uart-peripheral.md @@ -55,9 +55,9 @@ class MySoC(wiring.Component): m.d.comb += [ uart_phy_rx.divisor.eq(uart.rx.divisor), - uart.rx.data.payload.eq(uart_phy_rx.data), - uart.rx.data.valid.eq(uart_phy_rx.rdy), - uart_phy_rx.ack.eq(uart.rx.data.ready), + uart.rx.symbols.payload.eq(uart_phy_rx.data), + uart.rx.symbols.valid.eq(uart_phy_rx.rdy), + uart_phy_rx.ack.eq(uart.rx.symbols.ready), uart.rx.overflow.eq(uart_phy_rx.err.overflow), uart.rx.error.eq(uart_phy_rx.err.frame), @@ -66,16 +66,16 @@ class MySoC(wiring.Component): m.d.comb += [ uart_phy_tx.divisor.eq(uart.tx.divisor), - uart_phy_tx.data.eq(uart.tx.data.payload), - uart_phy_tx.ack.eq(uart.tx.data.valid), - uart.tx.data.ready.eq(uart_phy_tx.rdy), + uart_phy_tx.data.eq(uart.tx.symbols.payload), + uart_phy_tx.ack.eq(uart.tx.symbols.valid), + uart.tx.symbols.ready.eq(uart_phy_tx.rdy), ] # Add the UART peripheral to a CSR bus decoder: m.submodules.csr_decoder = csr_decoder = csr.Decoder(addr_width=31, data_width=8) - csr_decoder.add(uart.bus, addr=0x1000) + csr_decoder.add(uart.csr_bus, addr=0x1000) # ... @@ -227,7 +227,7 @@ Its members are defined as follows: { "rst": Out(1), "divisor": Out(unsigned(16)), - "symbol": In(wiring.Signature({ + "symbols": In(wiring.Signature({ "payload": Out(unsigned(symbol_width)), "valid": Out(1), "ready": In(1), @@ -250,7 +250,7 @@ Its members are defined as follows: { "rst": Out(1), "divisor": Out(unsigned(16)), - "symbol": Out(wiring.Signature({ + "symbols": Out(wiring.Signature({ "payload": Out(unsigned(symbol_width)), "valid": Out(1), "ready": In(1), @@ -271,8 +271,8 @@ The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the rec ```python3 { - "bus": In(csr.Signature(addr_width, data_width)), - "phy": Out(ReceiverPHYSignature(symbol_width)), + "csr_bus": In(csr.Signature(addr_width, data_width)), + "phy": Out(ReceiverPHYSignature(symbol_width)), } ``` @@ -287,8 +287,8 @@ The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the ```python3 { - "bus": In(csr.Signature(addr_width, data_width)), - "phy": Out(TransmitterPHYSignature(symbol_width)), + "csr_bus": In(csr.Signature(addr_width, data_width)), + "phy": Out(TransmitterPHYSignature(symbol_width)), } ``` @@ -304,9 +304,9 @@ The `uart.Peripheral` class is a `wiring.Component` implementing an UART periphe ```python3 { - "bus": In(csr.Signature(addr_width, data_width)), - "rx": Out(ReceiverPHYSignature(symbol_width)), - "tx": Out(TransmitterPHYSignature(symbol_width)), + "csr_bus": In(csr.Signature(addr_width, data_width)), + "rx": Out(ReceiverPHYSignature(symbol_width)), + "tx": Out(TransmitterPHYSignature(symbol_width)), } ``` From 0066536fc8c4e7e7d56c8bbfad67e8fadc860005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 2 Apr 2024 12:35:30 +0200 Subject: [PATCH 9/9] RFC #60: replace `Control/`Divisor` with `Config`/`PhyConfig`. Also: - Shorten API names (`ReceiverPHYSignature` -> `RxPhySignature`, etc). - Replace `symbol_width` with `symbol_shape`. - Use streams in `RxPhySignature` and `TxPhySignature`. --- text/0060-soc-uart-peripheral.md | 202 ++++++++---------- .../{reg-control.svg => reg-config.svg} | 0 .../{reg-divisor.svg => reg-phy_config.svg} | 2 +- 3 files changed, 93 insertions(+), 111 deletions(-) rename text/0060-soc-uart-peripheral/{reg-control.svg => reg-config.svg} (100%) rename text/0060-soc-uart-peripheral/{reg-divisor.svg => reg-phy_config.svg} (89%) diff --git a/text/0060-soc-uart-peripheral.md b/text/0060-soc-uart-peripheral.md index 2066b26..eff7ab9 100644 --- a/text/0060-soc-uart-peripheral.md +++ b/text/0060-soc-uart-peripheral.md @@ -40,7 +40,10 @@ class MySoC(wiring.Component): uart_divisor = int(platform.default_clk_frequency / 115200) - m.submodules.uart = uart = uart.Peripheral(divisor_init=uart_divisor, addr_width=8, data_width=8) + uart = uart.Peripheral(addr_width=8, data_width=8, symbol_shape=unsigned(8), + phy_config_shape=unsigned(16), phy_config_init=uart_divisor) + + m.submodules.uart = uart # Instantiate and connect the UART PHYs: @@ -53,7 +56,7 @@ class MySoC(wiring.Component): m.submodules.uart_phy_tx = ResetInserter(uart.tx.rst)(uart_phy_tx) m.d.comb += [ - uart_phy_rx.divisor.eq(uart.rx.divisor), + uart_phy_rx.divisor.eq(uart.rx.phy_config), uart.rx.symbols.payload.eq(uart_phy_rx.data), uart.rx.symbols.valid.eq(uart_phy_rx.rdy), @@ -64,7 +67,7 @@ class MySoC(wiring.Component): ] m.d.comb += [ - uart_phy_tx.divisor.eq(uart.tx.divisor), + uart_phy_tx.divisor.eq(uart.tx.phy_config), uart_phy_tx.data.eq(uart.tx.symbols.payload), uart_phy_tx.ack.eq(uart.tx.symbols.valid), @@ -87,18 +90,34 @@ class MySoC(wiring.Component): #### Receiver -##### Control (read/write) +##### Config (read/write) -bf([
          { name: 'enable', bits: 1, attr: 'RW' },
          { bits: 7, attr: 'ResR0W0' },
      ], {bits: 8}) -`Control.enable` is initialized to 0 on reset. +`Config.enable` is initialized to 0 on reset. + +- If `Config.enable` is 0, the receiver PHY should be held in reset state. +- If `Config.enable` is 1, the receiver PHY should operate normally. + +##### PhyConfig (read/write) + +bf([
+         {name: 'phy_config', bits: 16, attr: 'RW'},
+     ], {bits: 16}) -- If `Control.enable` is 0, the receiver PHY should be held in reset state. -- If `Control.enable` is 1, the receiver PHY should operate normally. +The `PhyConfig` register exposes an implementation-specific mechanism to configure the receiver PHY, such as its baudrate. Its shape is given by the `phy_config_shape` parameter (`unsigned(16)` in the above example). + +An implementation may choose to not use the `PhyConfig` register and configure its PHY through unspecified means. + +- If `Config.enable` is 0, `PhyConfig` is read/write. +- If `Config.enable` is 1, `PhyConfig` is read-only. + +`PhyConfig` is initialized to `phy_config_init` on reset. ##### Status (read/write) @@ -116,29 +135,7 @@ class MySoC(wiring.Component): `Status.overflow` and `Status.error` are initialized to 0 on reset. -The value of the `Status` register is not affected by `Control.enable`. - -##### Divisor (read/write) - -bf([
-         {name: 'divisor', bits: 16, attr: 'RW'},
-     ], {bits: 16}) - -The `Divisor` register exposes an implementation-specific mechanism to control the receiver baudrate. - -For example, assuming a clock frequency of 100MHz, an implementation may configure its baudrate like so: - -```python3 -baudrate = int(100e6 / divisor) -``` - -An implementation may also choose to ignore the `Divisor` register and configure the baudrate through unspecified means. - -- If `Control.enable` is 0, `Divisor` is read/write. -- If `Control.enable` is 1, `Divisor` is read-only. - -`Divisor` is initialized to `divisor_init` on reset. +`Status.overflow` and `Status.error` are not reset when writing 0 to `Config.enable`. ##### Data (read-only) @@ -147,25 +144,38 @@ An implementation may also choose to ignore the `Divisor` register and configure {name: 'data', bits: 8, attr: 'R'}, ], {bits: 8})"> -- If `Control.enable` is 0 or `Status.ready` is 0: +The `Data` register can be read to consume symbols from the receive buffer. Its shape is given by the `symbol_shape` parameter (`unsigned(8)` in the above example). + +- If `Config.enable` is 0 or `Status.ready` is 0: * reading from `Data` has no side-effect and returns an unspecified value. -- If `Control.enable` is 1 and `Status.ready` is 1: +- If `Config.enable` is 1 and `Status.ready` is 1: * reading from `Data` consumes one symbol from the receive buffer and returns it. #### Transmitter -##### Control (read/write) +##### Config (read/write) -bf([
          { name: 'enable', bits: 1, attr: 'RW' },
          { bits: 7, attr: 'ResR0W0' },
      ], {bits: 1}) -`Control.enable` is initialized to 0 on reset. +`Config.enable` is initialized to 0 on reset. + +- If `Config.enable` is 0, the transmitter PHY should be held in reset state. +- If `Config.enable` is 1, the transmitter PHY should operate normally. + +##### PhyConfig (read/write) + +The `PhyConfig` register exposes an implementation-specific mechanism to configure the transmitter PHY, such as its baudrate. Its shape is given by the `phy_config_shape` parameter (`unsigned(16)` in the above example). + +An implementation may choose to not use the `PhyConfig` register and configure its PHY through unspecified means. -- If `Control.enable` is 0, the transmitter PHY should be held in reset state. -- If `Control.enable` is 1, the transmitter PHY should operate normally. +- If `Config.enable` is 0, `PhyConfig` is read/write. +- If `Config.enable` is 1, `PhyConfig` is read-only. + +`PhyConfig` is initialized to `phy_config_init` on reset. ##### Status (read-only) @@ -177,28 +187,6 @@ An implementation may also choose to ignore the `Divisor` register and configure - `Status.ready` indicates that the transmit buffer has available space for at least one character. -##### Divisor (read/write) - -bf([
-         {name: 'divisor', bits: 16, attr: 'RW'},
-     ], {bits: 16}) - -The `Divisor` register exposes an implementation-specific mechanism to control the transmitter baudrate. - -For example, assuming a clock frequency of 100MHz, an implementation may configure its baudrate like so: - -```python3 -baudrate = int(100e6 / divisor) -``` - -An implementation may also choose to ignore the `Divisor` register and configure the baudrate through unspecified means. - -- If `Control.enable` is 0, `Divisor` is read/write. -- If `Control.enable` is 1, `Divisor` is read-only. - -`Divisor` is initialized to `divisor_init` on reset. - ##### Data (write-only) -- If `Status.rdy` is 1, writing to `Data` adds one character to the transmit buffer. +The `Data` register can be written to append symbols to the transmit buffer. Its shape is given by the `symbol_shape` parameter (`unsigned(8)` in the above example). -- If `Control.enable` is 0 or `Status.ready` is 0: +- If `Config.enable` is 0 or `Status.ready` is 0: * writing to `Data` has no side-effect. -- If `Control.enable` is 1 and `Status.ready` is 1: +- If `Config.enable` is 1 and `Status.ready` is 1: * writing to `Data` adds one symbol to the transmit buffer. ## Reference-level explanation [reference-level-explanation]: #reference-level-explanation -### `amaranth_soc.uart.ReceiverPHYSignature` +### `amaranth_soc.uart.RxPhySignature` -The `uart.ReceiverPHYSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its receiver PHY, with: -- a `.__init__(self, *, symbol_width)` constructor, where `symbol_width` is a positive integer. +The `uart.RxPhySignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its receiver PHY, with: +- a `.__init__(self, phy_config_shape, symbol_shape)` constructor, where `phy_config_shape` and `symbol_shape` are shape-like objects. Its members are defined as follows: ```python3 { "rst": Out(1), - "divisor": Out(unsigned(16)), - "symbols": In(wiring.Signature({ - "payload": Out(unsigned(symbol_width)), - "valid": Out(1), - "ready": In(1), - })), + "config": Out(phy_config_shape), + "symbols": In(stream.Signature(symbol_shape)), "overflow": In(1), "error": In(1), } ``` -The `rst` port is driven to 1 if `Control.enable` is 0, and 0 if `Control.enable` is 1. +- The `rst` port is driven to 1 if `Config.enable` is 0, and 0 if `Config.enable` is 1. +- The `config` remains constant if `rst` is 0. +- The `overflow` port is pulsed for one clock cycle if a symbol was received before the previous one is acknowledged (i.e. before `symbols.ready` is high). +- The `error` port is pulsed for one clock cycle in case of an unspecified error, specific to the PHY implementation. -### `amaranth_soc.uart.TransmitterPHYSignature` +### `amaranth_soc.uart.TxPhySignature` -The `uart.TransmitterSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its transmitter PHY, with: -- a `.__init__(self, *, symbol_width)` constructor, where `symbol_width` is a positive integer. +The `uart.TxPhySignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its transmitter PHY, with: +- a `.__init__(self, phy_config_shape, symbol_shape)` constructor, where `phy_config_shape` and `symbol_shape` are shape-like objects. Its members are defined as follows: ```python3 { "rst": Out(1), - "divisor": Out(unsigned(16)), - "symbols": Out(wiring.Signature({ - "payload": Out(unsigned(symbol_width)), - "valid": Out(1), - "ready": In(1), - })), + "config": Out(phy_config_shape), + "symbols": Out(stream.Signature(symbol_shape)), } ``` -The `rst` port is driven to 1 if `Control.enable` is 0, and 0 if `Control.enable` is 1. +- The `rst` port is driven to 1 if `Config.enable` is 0, and 0 if `Config.enable` is 1. +- The `config` remains constant if `rst` is 0. -### `amaranth_soc.uart.ReceiverPeripheral` +### `amaranth_soc.uart.RxPeripheral` -The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: -- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, symbol_width=8)` constructor, where: - * `divisor_init` is a positive integer used as initial values for `Divisor.divisor`. +The `uart.RxPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: +- a `.__init__(self, *, addr_width, data_width=8, name=None, phy_config_shape=unsigned(16), phy_config_init=0, symbol_shape=unsigned(8))` constructor, where: * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. - * `symbol_width` is a positive integer passed to `Data` and `ReceiverPHYSignature`. + * `phy_config_shape` is the shape of the single-field `PhyConfig` register. + * `phy_config_init` is the initial value of the single-field `PhyConfig` register. + * `symbol_shape` is the shape of the single-field `Data` register. - a `.signature` property, that returns a `wiring.Signature` with the following members: ```python3 { "csr_bus": In(csr.Signature(addr_width, data_width)), - "phy": Out(ReceiverPHYSignature(symbol_width)), + "phy": Out(RxPhySignature(phy_config_shape, symbol_shape)), } ``` -### `amaranth_soc.uart.TransmitterPeripheral` +### `amaranth_soc.uart.TxPeripheral` -The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the transmitter of an UART peripheral, with: -- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, symbol_width=8)` constructor, where: - * `divisor_init` is a positive integer used as initial values for `Divisor.divisor`. +The `uart.TxPeripheral` class is a `wiring.Component` implementing the transmitter of an UART peripheral, with: +- a `.__init__(self, *, addr_width, data_width=8, name=None, phy_config_shape=unsigned(16), phy_config_init=0, symbol_shape=unsigned(8))` constructor, where: * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. - * `symbol_width` is a positive integer passed to `Data` and `TransmitterPHYSignature`. + * `phy_config_shape` is the shape of the single-field `PhyConfig` register. + * `phy_config_init` is the initial value of the single-field `PhyConfig` register. + * `symbol_shape` is the shape of the single-field `Data` register. - a `.signature` property, that returns a `wiring.Signature` with the following members: ```python3 { "csr_bus": In(csr.Signature(addr_width, data_width)), - "phy": Out(TransmitterPHYSignature(symbol_width)), + "phy": Out(TxPhySignature(phy_config_shape, symbol_shape)), } ``` ### `amaranth_soc.uart.Peripheral` The `uart.Peripheral` class is a `wiring.Component` implementing an UART peripheral, with: -- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, symbol_width=8)` constructor, where: - * `divisor_init` is a positive integer used as initial values for `Divisor.divisor`. - * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. `addr_width` must be at least 1. The peripheral address space is split in two, with the lower half occupied by a `ReceiverPeripheral` and the upper by a `TransmitterPeripheral`. - * `symbol_width` is a positive integer passed to `ReceiverPeripheral`, `TransmitterPeripheral`, `ReceiverPHYSignature` and `TransmitterPHYSignature`. +- a `.__init__(self, *, addr_width, data_width=8, name=None, phy_config_shape=unsigned(16), phy_config_init=0, symbol_shape=unsigned(8))` constructor, where: + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. `addr_width` must be at least 1. The peripheral address space is split in two, with the lower half occupied by a `RxPeripheral` and the upper by a `TxPeripheral`. + * `phy_config_shape` and `phy_config_init` are the shape and initial value of the `PhyConfig` registers of `RxPeripheral` and `TxPeripheral`. + * `symbol_shape` is the shape of the `Data` registers of `RxPeripheral` and `TxPeripheral`. - a `.signature` property, that returns a `wiring.Signature` with the following members: ```python3 { "csr_bus": In(csr.Signature(addr_width, data_width)), - "rx": Out(ReceiverPHYSignature(symbol_width)), - "tx": Out(TransmitterPHYSignature(symbol_width)), + "rx": Out(RxPhySignature(phy_config_shape, symbol_shape)), + "tx": Out(TxPhySignature(phy_config_shape, symbol_shape)), } ``` @@ -314,24 +300,20 @@ The `uart.Peripheral` class is a `wiring.Component` implementing an UART periphe [drawbacks]: #drawbacks - This design decouples the UART peripheral from its PHY, which must be provided by the user. -- The receiver and transmitter have separate `Divider` registers, despite using identical values - in most cases. -- Configuring the baudrate through the `Divisor` register requires knowledge of the clock frequency used by the peripheral. +- An `uart.Peripheral` has separate `PhyConfig` registers for its receiver and transmitter, despite using common values in most cases due to their symmetry. +- A `PhyConfig` register has a single field whose shape is user-provided, even though it may contain multiple values. Amaranth SoC will have to take this into account when generating a BSP. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives - This design is intended to be minimal yet useful for the most common use-cases (i.e. 8-N-1). - Decoupling the peripheral from the PHY allows flexibility in implementations. For example, it is easy to add FIFOs between the PHYs and the peripheral. -- A standalone `ReceiverPeripheral` or `TransmitterPeripheral` can be instantiated. - -- The choice of a 16-bit divisor with an (otherwise) unspecified encoding allows implementation freedom: - * some may not care about a clock divisor at all (e.g. a behavioral model of an UART PHY, interfacing with a pseudo-TTY). - * some may provide their own divisor encoding scheme (e.g. a 13-bit base value with a 3-bit scale, that can cover common frequency/baudrate combinations with a [<1% error rate](https://github.com/amaranth-lang/rfcs/files/14672989/baud.py.txt) (credit: [@whitequark](https://github.com/whitequark))). - +- A standalone `RxPeripheral` or `TxPeripheral` can be instantiated. +- The choice of a parameterized shape for the `PhyConfig` register facilitates interoperability with PHY implementations. Some may not rely on this register and configure themselves through alternate means (or not at all). - As an alternative: * implement the PHY in the peripheral itself, and expose pin interfaces in a similar manner as the GPIO peripheral of [RFC 49](https://amaranth-lang.org/rfcs/0049-soc-gpio-peripheral.html). + * do not allow `PhyConfig` to be parameterized and provide its layout ourselves. ## Prior art [prior-art]: #prior-art diff --git a/text/0060-soc-uart-peripheral/reg-control.svg b/text/0060-soc-uart-peripheral/reg-config.svg similarity index 100% rename from text/0060-soc-uart-peripheral/reg-control.svg rename to text/0060-soc-uart-peripheral/reg-config.svg diff --git a/text/0060-soc-uart-peripheral/reg-divisor.svg b/text/0060-soc-uart-peripheral/reg-phy_config.svg similarity index 89% rename from text/0060-soc-uart-peripheral/reg-divisor.svg rename to text/0060-soc-uart-peripheral/reg-phy_config.svg index 7e96cfe..e3d14b6 100644 --- a/text/0060-soc-uart-peripheral/reg-divisor.svg +++ b/text/0060-soc-uart-peripheral/reg-phy_config.svg @@ -1 +1 @@ -015divisorRW +015phy_configRW