|
| 1 | +- Start Date: (fill me in with today's date, YYYY-MM-DD) |
| 2 | +- RFC PR: [amaranth-lang/rfcs#0000](https://github.com/amaranth-lang/rfcs/pull/0000) |
| 3 | +- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) |
| 4 | + |
| 5 | +# UART peripheral RFC |
| 6 | + |
| 7 | +## Summary |
| 8 | +[summary]: #summary |
| 9 | + |
| 10 | +Add a SoC peripheral for UART devices. |
| 11 | + |
| 12 | +## Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +An UART is a generally useful peripheral for serial communication between devices. |
| 16 | + |
| 17 | +## Guide-level explanation |
| 18 | +[guide-level-explanation]: #guide-level-explanation |
| 19 | + |
| 20 | +### Usage |
| 21 | + |
| 22 | +```python3 |
| 23 | +from amaranth import * |
| 24 | +from amaranth.lib import wiring |
| 25 | +from amaranth.lib.wiring import connect |
| 26 | + |
| 27 | +from amaranth_stdio.serial import AsyncSerialRX, AsyncSerialTX |
| 28 | + |
| 29 | +from amaranth_soc import csr |
| 30 | +from amaranth_soc import uart |
| 31 | + |
| 32 | + |
| 33 | +class MySoC(wiring.Component): |
| 34 | + def elaborate(self, platform): |
| 35 | + m = Module() |
| 36 | + |
| 37 | + # ... |
| 38 | + |
| 39 | + # Instantiate an UART peripheral: |
| 40 | + |
| 41 | + uart_divisor = int(platform.default_clk_frequency / 115200) |
| 42 | + |
| 43 | + m.submodules.uart = uart = uart.Peripheral(divisor_init=uart_divisor, addr_width=8, data_width=8) |
| 44 | + |
| 45 | + # Instantiate and connect the UART PHYs: |
| 46 | + |
| 47 | + uart_pins = platform.request("uart", 0) |
| 48 | + |
| 49 | + uart_phy_rx = AsyncSerialRX(uart_divisor, divisor_bits=16, pins=uart_pins) |
| 50 | + uart_phy_tx = AsyncSerialTX(uart_divisor, divisor_bits=16, pins=uart_pins) |
| 51 | + |
| 52 | + m.submodules.uart_phy_rx = uart_phy_rx |
| 53 | + m.submodules.uart_phy_tx = uart_phy_tx |
| 54 | + |
| 55 | + m.d.comb += [ |
| 56 | + uart_phy_rx.divisor.eq(uart.rx.divisor), |
| 57 | + |
| 58 | + uart.rx.stream.data.eq(uart_phy_rx.data), |
| 59 | + uart.rx.stream.valid.eq(uart_phy_rx.rdy), |
| 60 | + uart_phy_rx.ack.eq(uart.stream.ready), |
| 61 | + |
| 62 | + uart.rx.overflow.eq(uart_phy_rx.err.overflow), |
| 63 | + uart.rx.error.eq(uart_phy_rx.err.frame), |
| 64 | + ] |
| 65 | + |
| 66 | + m.d.comb += [ |
| 67 | + uart_phy_tx.divisor.eq(uart.tx.divisor), |
| 68 | + |
| 69 | + uart_phy_tx.data.eq(uart.tx.stream.data), |
| 70 | + uart_phy_tx.ack.eq(uart.tx.stream.valid), |
| 71 | + uart.tx.stream.ready.eq(uart_phy_tx.rdy), |
| 72 | + ] |
| 73 | + |
| 74 | + # Add the UART peripheral to a CSR bus decoder: |
| 75 | + |
| 76 | + m.submodules.csr_decoder = csr_decoder = csr.Decoder(addr_width=31, data_width=8) |
| 77 | + |
| 78 | + csr_decoder.add(uart.bus, addr=0x1000) |
| 79 | + |
| 80 | + # ... |
| 81 | + |
| 82 | + return m |
| 83 | + |
| 84 | +``` |
| 85 | + |
| 86 | +### Registers |
| 87 | + |
| 88 | +#### Receiver |
| 89 | + |
| 90 | +##### Enable (read/write) |
| 91 | + |
| 92 | +<img src="./0000-soc-uart-peripheral/reg-enable.svg" |
| 93 | + alt="bf([ |
| 94 | + {name: 'en', bits: 1, attr: 'RW'}, |
| 95 | + ], {bits: 1})"> |
| 96 | + |
| 97 | +- If `Enable.en` is 0, the receiver is held in reset state. |
| 98 | + |
| 99 | +- `Enable.en` is initialized to 0. |
| 100 | + |
| 101 | +##### Divisor (read/write) |
| 102 | + |
| 103 | +<img src="./0000-soc-uart-peripheral/reg-divisor.svg" |
| 104 | + alt="bf([ |
| 105 | + {name: 'cnt', bits: 13, attr: 'RW'}, |
| 106 | + {name: 'psc', bits: 3, attr: 'RW'}, |
| 107 | + ], {bits: 16})"> |
| 108 | + |
| 109 | +- If `Enable.en` is 0, `Divisor` is read-only. |
| 110 | + |
| 111 | +- `Divisor.cnt` is initialized to `divisor_cnt_init`. |
| 112 | +- `Divisor.psc` is initialized to `divisor_psc_init`. |
| 113 | + |
| 114 | +Assuming a clock frequency of 100MHz, the receiver baudrate is computed like so: |
| 115 | + |
| 116 | +```python3 |
| 117 | +baudrate = ((1 << psc) * 100e6) // (cnt + 1) |
| 118 | +``` |
| 119 | + |
| 120 | +##### Status (read/write) |
| 121 | + |
| 122 | +<img src="./0000-soc-uart-peripheral/reg-rx-status.svg" |
| 123 | + alt="bf([ |
| 124 | + { name: 'rdy', bits: 1, attr: 'R' }, |
| 125 | + { name: 'ovf', bits: 1, attr: 'RW1C' }, |
| 126 | + { name: 'err', bits: 1, attr: 'RW1C' }, |
| 127 | + { bits: 5, attr: 'ResR0W0' }, |
| 128 | + ], {bits: 8})"> |
| 129 | + |
| 130 | +- `Status.rdy` indicates that the receive buffer contains at least one character. |
| 131 | +- `Status.ovf` is set if a new frame was received while the receive buffer is full. |
| 132 | +- `Status.err` is set if any implementation-specific error condition occured. |
| 133 | + |
| 134 | +- `Status.ovf` and `Status.err` are initialized to 0. |
| 135 | + |
| 136 | +##### Data (read-only) |
| 137 | + |
| 138 | +<img src="./0000-soc-uart-peripheral/reg-rx-data.svg" |
| 139 | + alt="bf([ |
| 140 | + {name: 'data', bits: 8, attr: 'R'}, |
| 141 | + ], {bits: 8})"> |
| 142 | + |
| 143 | +- If `Status.rdy` is 1, reading from `Data` consumes one character from the receive buffer. |
| 144 | + |
| 145 | +#### Transmitter |
| 146 | + |
| 147 | +##### Enable (read/write) |
| 148 | + |
| 149 | +<img src="./0000-soc-uart-peripheral/reg-enable.svg" |
| 150 | + alt="bf([ |
| 151 | + {name: 'en', bits: 1, attr: 'RW'}, |
| 152 | + ], {bits: 1})"> |
| 153 | + |
| 154 | +- If `Enable.en` is 0, the transmitter is held in reset state. |
| 155 | + |
| 156 | +- `Enable.en` is initialized to 0. |
| 157 | + |
| 158 | +##### Divisor (read/write) |
| 159 | + |
| 160 | +<img src="./0000-soc-uart-peripheral/reg-divisor.svg" |
| 161 | + alt="bf([ |
| 162 | + {name: 'cnt', bits: 13, attr: 'RW'}, |
| 163 | + {name: 'psc', bits: 3, attr: 'RW'}, |
| 164 | + ], {bits: 16})"> |
| 165 | + |
| 166 | +- If `Enable.en` is 0, `Divisor` is read-only. |
| 167 | + |
| 168 | +- `Divisor.cnt` is initialized to `divisor_cnt_init`. |
| 169 | +- `Divisor.psc` is initialized to `divisor_psc_init`. |
| 170 | + |
| 171 | +Assuming a clock frequency of 100MHz, the transmitter baudrate is computed like so: |
| 172 | + |
| 173 | +```python3 |
| 174 | +baudrate = ((2 ** psc) * 100e6) // (cnt + 1) |
| 175 | +``` |
| 176 | + |
| 177 | +##### Status (read-only) |
| 178 | + |
| 179 | +<img src="./0000-soc-uart-peripheral/reg-tx-status.svg" |
| 180 | + alt="bf([ |
| 181 | + { name: 'rdy', bits: 1, attr: 'R' }, |
| 182 | + { bits: 7, attr: 'ResR0W0' }, |
| 183 | + ], {bits: 8})"> |
| 184 | + |
| 185 | +- `Status.rdy` indicates that the transmit buffer has available space for at least one character. |
| 186 | + |
| 187 | +##### Data (write-only) |
| 188 | + |
| 189 | +<img src="./0000-soc-uart-peripheral/reg-tx-data.svg" |
| 190 | + alt="bf([ |
| 191 | + {name: 'data', bits: 8, attr: 'W'}, |
| 192 | + ], {bits: 8})"> |
| 193 | + |
| 194 | +- If `Status.rdy` is 1, writing to `Data` adds one character to the transmit buffer. |
| 195 | + |
| 196 | +## Reference-level explanation |
| 197 | +[reference-level-explanation]: #reference-level-explanation |
| 198 | + |
| 199 | +### `amaranth_soc.uart.ReceiverPHYSignature` |
| 200 | + |
| 201 | +The `uart.ReceiverPHYSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its receiver PHY, with: |
| 202 | +- a `.__init__(self, *, data_bits)` constructor, where `data_bits` is a non-negative integer. |
| 203 | + |
| 204 | +Its members are defined as follows: |
| 205 | + |
| 206 | +```python3 |
| 207 | +{ |
| 208 | + "divisor": In(unsigned(20)), |
| 209 | + "stream": Out(wiring.Signature({ |
| 210 | + "data": Out(unsigned(data_bits)), |
| 211 | + "valid": Out(unsigned(1)), |
| 212 | + "ready": In(unsigned(1)), |
| 213 | + })), |
| 214 | + "overflow": Out(unsigned(1)), |
| 215 | + "error": Out(unsigned(1)), |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +### `amaranth_soc.uart.TransmitterPHYSignature` |
| 220 | + |
| 221 | +The `uart.TransmitterSignature` class is a `wiring.Signature` describing the interface between the UART peripheral and its transmitter PHY, with: |
| 222 | +- a `.__init__(self, *, data_bits)` constructor, where `data_bits` is a non-negative integer. |
| 223 | + |
| 224 | +Its members are defined as follows: |
| 225 | + |
| 226 | +```python3 |
| 227 | +{ |
| 228 | + "divisor": In(unsigned(20)), |
| 229 | + "stream": In(wiring.Signature({ |
| 230 | + "data": Out(unsigned(data_bits)), |
| 231 | + "valid": Out(unsigned(1)), |
| 232 | + "ready": In(unsigned(1)), |
| 233 | + })), |
| 234 | +} |
| 235 | +``` |
| 236 | + |
| 237 | +### `amaranth_soc.uart.ReceiverPeripheral` |
| 238 | + |
| 239 | +The `uart.ReceiverPeripheral` class is a `wiring.Component` implementing the receiver of an UART peripheral, with: |
| 240 | +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: |
| 241 | + * `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. |
| 242 | + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. |
| 243 | + * `data_bits` is a non-negative integer passed to `Data` and `ReceiverPHYSignature`. |
| 244 | +- a `.signature` property, that returns a `wiring.Signature` with the following members: |
| 245 | + |
| 246 | +```python3 |
| 247 | +{ |
| 248 | + "bus": In(csr.Signature(addr_width, data_width)), |
| 249 | + "phy": In(ReceiverPHYSignature(data_bits)), |
| 250 | +} |
| 251 | +``` |
| 252 | + |
| 253 | +### `amaranth_soc.uart.TransmitterPeripheral` |
| 254 | + |
| 255 | +The `uart.TransmitterPeripheral` class is a `wiring.Component` implementing the transmitter of an UART peripheral, with: |
| 256 | +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: |
| 257 | + * `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. |
| 258 | + * `addr_width`, `data_width` and `name` are passed to a `csr.Builder`. |
| 259 | + * `data_bits` is a non-negative integer passed to `Data` and `TransmitterPHYSignature`. |
| 260 | +- a `.signature` property, that returns a `wiring.Signature` with the following members: |
| 261 | + |
| 262 | +```python3 |
| 263 | +{ |
| 264 | + "bus": In(csr.Signature(addr_width, data_width)), |
| 265 | + "phy": In(TransmitterPHYSignature(data_bits)), |
| 266 | +} |
| 267 | +``` |
| 268 | + |
| 269 | +### `amaranth_soc.uart.Peripheral` |
| 270 | + |
| 271 | +The `uart.Peripheral` class is a `wiring.Component` implementing an UART peripheral, with: |
| 272 | +- a `.__init__(self, *, divisor_init, addr_width, data_width=8, name=None, data_bits=8)` constructor, where: |
| 273 | + * `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. |
| 274 | + * `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`. |
| 275 | + * `data_bits` is a non-negative integer passed to `ReceiverPeripheral`, `TransmitterPeripheral`, `ReceiverPHYSignature` and `TransmitterPHYSignature`. |
| 276 | + |
| 277 | +- a `.signature` property, that returns a `wiring.Signature` with the following members: |
| 278 | + |
| 279 | +```python3 |
| 280 | +{ |
| 281 | + "bus": In(csr.Signature(addr_width, data_width)), |
| 282 | + "rx": In(ReceiverPHYSignature(data_bits)), |
| 283 | + "tx": In(TransmitterPHYSignature(data_bits)), |
| 284 | +} |
| 285 | +``` |
| 286 | + |
| 287 | +## Drawbacks |
| 288 | +[drawbacks]: #drawbacks |
| 289 | + |
| 290 | +- This design decouples the UART peripheral from its PHY, which must be provided by the user. |
| 291 | +- The receiver and transmitter have separate `Divider` registers, despite using identical values |
| 292 | + in most cases. |
| 293 | +- Configuring the baudrate through the `Divider` register requires knowledge of the clock frequency used by the peripheral. |
| 294 | + |
| 295 | +## Rationale and alternatives |
| 296 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 297 | + |
| 298 | +- This design is intended to be minimal and work reliably for the most common use-cases (i.e. 8-N-1). |
| 299 | +- Decoupling the peripheral from the PHY allows flexibility in implementations. For example, it is easy to add FIFOs between the PHYs and the peripheral. |
| 300 | +- A standalone `ReceiverPeripheral` or `TransmitterPeripheral` can be instantiated. |
| 301 | + |
| 302 | +- 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%. |
| 303 | + |
| 304 | +*TODO: a table showing frequency/baudrate combinations* |
| 305 | + |
| 306 | +- As an alternative: |
| 307 | + * implement the PHY in the peripheral itself, and expose pin interfaces in a similar manner as the GPIO peripheral of RFC 49. |
| 308 | + |
| 309 | +## Prior art |
| 310 | +[prior-art]: #prior-art |
| 311 | + |
| 312 | +UART peripherals are commonly found in microcontrollers. |
| 313 | + |
| 314 | +## Unresolved questions |
| 315 | +[unresolved-questions]: #unresolved-questions |
| 316 | + |
| 317 | +None. |
| 318 | + |
| 319 | +## Future possibilities |
| 320 | +[future-possibilities]: #future-possibilities |
| 321 | + |
| 322 | +- Add a separate 16550-compatible UART peripheral. |
| 323 | +- Expand this peripheral with additional features, such as: |
| 324 | + * parity |
| 325 | + * auto baudrate |
| 326 | + * oversampling |
| 327 | + * hardware flow control |
| 328 | + * interrupts |
| 329 | + * DMA |
0 commit comments