From 90078b1580af34d49d7a979d65e04384dea2cf68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 29 Mar 2024 14:52:29 +0100 Subject: [PATCH] Implement RFC 60: UART peripheral. --- amaranth_soc/uart.py | 602 +++++++++++++++++++++++++++++++++++++++++++ tests/test_uart.py | 463 +++++++++++++++++++++++++++++++++ 2 files changed, 1065 insertions(+) create mode 100644 amaranth_soc/uart.py create mode 100644 tests/test_uart.py diff --git a/amaranth_soc/uart.py b/amaranth_soc/uart.py new file mode 100644 index 0000000..7e7bbfb --- /dev/null +++ b/amaranth_soc/uart.py @@ -0,0 +1,602 @@ +from amaranth import * +from amaranth.lib import wiring +from amaranth.lib.wiring import In, Out, flipped, connect + +from . import csr + + +__all__ = ["RxPhySignature", "TxPhySignature", "RxPeripheral", "TxPeripheral", "Peripheral"] + + +class RxPhySignature(wiring.Signature): + """Receiver PHY signature. + + Parameters + ---------- + phy_config_shape : :ref:`shape-like ` + Shape of the `config` member of this interface. + symbol_shape : :ref:`shape-like ` + Shape of the `symbols.payload` member of this interface. + + Interface attributes + -------------------- + rst : :class:`Signal` + PHY reset. + config : :class:`Signal` + PHY configuration word. Its shape is given by the `phy_config_shape` parameter. Its value + must remain constant unless `rst` is high. + symbols : :class:`stream.Interface` + Symbol stream. The shape of its payload is given by the `symbol_shape` parameter. + overflow : :class:`Signal` + Overflow flag. Pulsed for one clock cycle if a symbol was received before the previous one + is acknowledged (i.e. before `symbols.ready` is high). + error : :class:`Signal` + Receiver error flag. Pulsed for one clock cycle in case of an implementation-specific error + (e.g. wrong parity bit). + """ + def __init__(self, phy_config_shape, symbol_shape): + super().__init__({ + "rst": Out(1), + "config": Out(phy_config_shape), + "symbols": In(wiring.Signature({ # TODO: replace with stream.Signature + "payload": Out(symbol_shape), + "valid": Out(1), + "ready": In(1), + })), + "overflow": In(1), + "error": In(1), + }) + + +class TxPhySignature(wiring.Signature): + """Transmitter PHY signature. + + Parameters + ---------- + phy_config_shape : :ref:`shape-like ` + Shape of the `config` member of this interface. + symbol_shape : :ref:`shape-like ` + Shape of the `symbols.payload` member of this interface. + + Interface attributes + -------------------- + rst : :class:`Signal` + PHY reset. + config : :class:`Signal` + PHY configuration. Its shape is given by the `phy_config_shape` parameter. Its value must + remain constant unless `rst` is high. + symbols : :class:`stream.Interface` + Symbol stream. The shape of its payload is given by the `symbol_shape` parameter. + """ + def __init__(self, phy_config_shape, symbol_shape): + super().__init__({ + "rst": Out(1), + "config": Out(phy_config_shape), + "symbols": Out(wiring.Signature({ # TODO: replace with stream.Signature + "payload": Out(symbol_shape), + "valid": Out(1), + "ready": In(1), + })), + }) + + +class _PhyConfigFieldAction(csr.FieldAction): + def __init__(self, shape, *, init=0): + super().__init__(shape, access="rw", members=( + ("data", Out(shape)), + ("w_en", In(unsigned(1))), + )) + self._storage = Signal(shape, init=init) + + def elaborate(self, platform): + m = Module() + + with m.If(self.w_en & self.port.w_stb): + m.d.sync += self._storage.eq(self.port.w_data) + + m.d.comb += [ + self.port.r_data.eq(self._storage), + self.data.eq(self._storage), + ] + + return m + + +class RxPeripheral(wiring.Component): + class Config(csr.Register, access="rw"): + """Peripheral configuration register. + + This :class:`Register` has the following fields: + + .. bitfield:: + :bits: 8 + + [ + { "name": "enable", "bits": 1, "attr": "RW" }, + { "bits": 7, "attr": "ResR0W0" }, + ] + + - If the ``enable`` field is 0, the PHY is held in reset state. + - If the ``enable`` field is 1, the PHY operates normally. + """ + enable: csr.Field(csr.action.RW, 1) + _unimp: csr.Field(csr.action.ResR0W0, 7) + + class PhyConfig(csr.Register, access="rw"): + """PHY configuration register. + + This :class:`Register` is writable if the ``enable`` field of :class:`RxPeripheral.Config` + is 0, and read-only otherwise. It has a single field with an implementation-specific shape + given by the ``phy_config_shape`` parameter. + + If ``phy_config_shape`` is ``unsigned(16)``, then the register has the following field: + + .. bitfield:: + :bits: 16 + + [ + { "name": "phy_config", "bits": 16, "attr": "RW" }, + ] + + Parameters + ---------- + phy_config_shape : :ref:`shape-like ` + Shape of the PHY configuration word. + phy_config_init : :class:`int` + Initial value of the PHY configuration word. + """ + def __init__(self, phy_config_shape, phy_config_init): + super().__init__(csr.Field(_PhyConfigFieldAction, phy_config_shape, + init=phy_config_init)) + + class Status(csr.Register, access="rw"): + """Status register. + + This :class:`Register` is read to be informed of available data or error conditions. + + It has the following fields: + + .. bitfield:: + :bits: 8 + + [ + { "name": "ready", "bits": 1, "attr": "R" }, + { "name": "overflow", "bits": 1, "attr": "RW1C" }, + { "name": "error", "bits": 1, "attr": "RW1C" }, + { "bits": 5, "attr": "ResR0W0" }, + ] + + - The ``ready`` field is set if the receiver buffer is non-empty. + - The ``overflow`` field is set and latched if a symbol is received while the receiver + buffer is full. + - The ``error`` field is set and latched if any implementation-specific error occured. + """ + ready: csr.Field(csr.action.R, 1) + overflow: csr.Field(csr.action.RW1C, 1) + error: csr.Field(csr.action.RW1C, 1) + _unimp: csr.Field(csr.action.ResR0W0, 5) + + class Data(csr.Register, access="r"): + """Data register. + + This :class:`Register` is read to consume data from the receiver buffer. It has a single + field with an implementation-specific shape given by the ``symbol_shape`` parameter. + + If ``symbol_shape`` is ``unsigned(8)``, then the register has the following field: + + .. bitfield:: + :bits: 8 + + [ + { "name": "data", "bits": 8, "attr": "R" }, + ] + + - If either the ``enable`` field of :class:`RxPeripheral.Config` or the ``ready`` field of + :class:`RxPeripheral.Status` are 0, reading from this register has no side-effect and + returns an unspecified value. + - If both the ``enable`` field of :class:`RxPeripheral.Config` and the ``ready`` field of + :class:`RxPeripheral.Status` are 1, reading from this register consumes one symbol from + the receiver buffer and returns it. + + Parameters + ---------- + symbol_shape : :ref:`shape-like ` + Shape of a symbol. + """ + def __init__(self, symbol_shape): + super().__init__(csr.Field(csr.action.R, symbol_shape)) + + """UART receiver peripheral. + + Parameters + ---------- + addr_width : :class:`int` + CSR bus address width. + data_width : :class:`int` + CSR bus data width. + name : :class:`str` + CSR bus window name. Optional. Defaults to ``None``. + phy_config_shape : :ref:`shape-like ` + Shape of the PHY configuration word. Optional. Defaults to ``unsigned(16)``. + phy_config_init : :class:`int` + Initial value of the PHY configuration word. Optional. Defaults to ``0``. + symbol_shape : :ref:`shape-like ` + Shape of a symbol. Optional. Defaults to ``unsigned(8)``. + + Attributes + ---------- + bus : :class:`csr.Interface` + CSR bus interface providing access to registers. + phy : :class:`wiring.PureInterface` of :class:`RxPhySignature` + Interface between the peripheral and its PHY. + """ + def __init__(self, *, addr_width, data_width, name=None, phy_config_shape=unsigned(16), + phy_config_init=0, symbol_shape=unsigned(8)): + regs = csr.Builder(addr_width=addr_width, data_width=data_width, name=name) + + self._config = regs.add("Config", self.Config()) + self._phy_config = regs.add("PhyConfig", self.PhyConfig(phy_config_shape, phy_config_init)) + self._status = regs.add("Status", self.Status()) + self._data = regs.add("Data", self.Data(symbol_shape)) + + self._bridge = csr.Bridge(regs.as_memory_map()) + + super().__init__({ + "bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)), + "phy": Out(RxPhySignature(phy_config_shape, symbol_shape)), + }) + self.bus.memory_map = self._bridge.bus.memory_map + + self._phy_config_shape = phy_config_shape + self._phy_config_init = phy_config_init + self._symbol_shape = symbol_shape + + @property + def phy_config_shape(self): + """Shape of the PHY configuration word. + + Returns + ------- + :ref:`shape-like ` + """ + return self._phy_config_shape + + @property + def phy_config_init(self): + """Initial value of the PHY configuration word. + + Returns + ------- + :class:`int` + """ + return self._phy_config_init + + @property + def symbol_shape(self): + """Shape of a symbol. + + Returns + ------- + :ref:`shape-like ` + """ + return self._symbol_shape + + def elaborate(self, platform): + m = Module() + m.submodules.bridge = self._bridge + + connect(m, flipped(self.bus), self._bridge.bus) + + m.d.comb += [ + self.phy.rst.eq(~self._config.f.enable.data), + + self._phy_config.f.w_en.eq(self.phy.rst), + self.phy.config.eq(self._phy_config.f.data), + + self._status.f.ready.r_data.eq(self.phy.symbols.valid), + self._data.f.r_data.eq(self.phy.symbols.payload), + self.phy.symbols.ready.eq(self._data.f.r_stb), + + self._status.f.overflow.set.eq(self.phy.overflow), + self._status.f.error.set.eq(self.phy.error), + ] + + return m + + +class TxPeripheral(wiring.Component): + class Config(csr.Register, access="rw"): + """Peripheral configuration register. + + This :class:`Register` has the following fields: + + .. bitfield:: + :bits: 8 + + [ + { "name": "enable", "bits": 1, "attr": "RW" }, + { "bits": 7, "attr": "ResR0W0" }, + ] + + - If the ``enable`` bit is 0, the PHY is held in reset state. + - If the ``enable`` bit is 1, the PHY operates normally. + """ + enable: csr.Field(csr.action.RW, 1) + _unimp: csr.Field(csr.action.ResR0W0, 7) + + class PhyConfig(csr.Register, access="rw"): + """PHY configuration register. + + This :class:`Register` is writable if the ``enable`` field of :class:`TxPeripheral.Config` + is 0, and read-only otherwise. It has a single field with an implementation-specific shape + given by the ``phy_config_shape`` parameter. + + If ``phy_config_shape`` is ``unsigned(16)``, then the register has the following field: + + .. bitfield:: + :bits: 16 + + [ + { "name": "phy_config", "bits": 16, "attr": "RW" }, + ] + + Parameters + ---------- + phy_config_shape : :ref:`shape-like ` + Shape of the PHY configuration word. + phy_config_init : :class:`int` + Initial value of the PHY configuration word. + """ + def __init__(self, phy_config_shape, phy_config_init): + super().__init__(csr.Field(_PhyConfigFieldAction, phy_config_shape, + init=phy_config_init)) + + class Status(csr.Register, access="r"): + """Status register. + + This :class:`Register` is read to be informed of when the transmitter can accept data. + + It has the following fields: + + .. bitfield:: + :bits: 8 + + [ + { "name": "ready", "bits": 1, "attr": "R" }, + { "bits": 7, "attr": "ResR0W0" }, + ] + + - The ``ready`` field is set if the transmitter buffer is non-full. + """ + ready: csr.Field(csr.action.R, 1) + _unimp: csr.Field(csr.action.ResR0W0, 7) + + class Data(csr.Register, access="w"): + """Data register. + + This :class:`Register` is written to append data to the transmitter buffer. It has a single + field with an implementation-specific shape given by the ``symbol_shape`` parameter. + + If ``symbol_shape`` is ``unsigned(8)``, then the register has the following field: + + .. bitfield:: + :bits: 8 + + [ + { "name": "data", "bits": 8, "attr": "W" }, + ] + + - If either the ``enable`` field of :class:`TxPeripheral.Config` or the ``ready`` field of + :class:`TxPeripheral.Status` are 0, writing to this register has no side-effect. + - If both the ``enable`` field of :class:`TxPeripheral.Config` and the ``ready`` field of + :class:`TxPeripheral.Status` are 1, writing to this register appends one symbol to the + transmitter buffer. + + Parameters + ---------- + symbol_shape : :ref:`shape-like ` + Shape of a symbol. + """ + def __init__(self, symbol_shape): + super().__init__(csr.Field(csr.action.W, symbol_shape)) + + """UART transmitter peripheral. + + Parameters + ---------- + addr_width : :class:`int` + CSR bus address width. + data_width : :class:`int` + CSR bus data width. + name : :class:`str` + CSR bus window name. Optional. Defaults to ``None``. + phy_config_shape : :ref:`shape-like ` + Shape of the PHY configuration word. Optional. Defaults to ``unsigned(16)``. + phy_config_init : :class:`int` + Initial value of the PHY configuration word. Optional. Defaults to ``0``. + symbol_shape : :ref:`shape-like ` + Shape of a symbol. Optional. Defaults to ``unsigned(8)``. + + Attributes + ---------- + bus : :class:`csr.Interface` + CSR bus interface providing access to registers. + phy : :class:`wiring.PureInterface` of :class:`TxPhySignature` + Interface between the peripheral and its PHY. + """ + def __init__(self, *, addr_width, data_width=8, name=None, phy_config_shape=unsigned(16), + phy_config_init=0, symbol_shape=unsigned(8)): + regs = csr.Builder(addr_width=addr_width, data_width=data_width, name=name) + + self._config = regs.add("Config", self.Config()) + self._phy_config = regs.add("PhyConfig", self.PhyConfig(phy_config_shape, phy_config_init)) + self._status = regs.add("Status", self.Status()) + self._data = regs.add("Data", self.Data(symbol_shape)) + + self._bridge = csr.Bridge(regs.as_memory_map()) + + super().__init__({ + "bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)), + "phy": Out(TxPhySignature(phy_config_shape, symbol_shape)), + }) + self.bus.memory_map = self._bridge.bus.memory_map + + self._phy_config_shape = phy_config_shape + self._phy_config_init = phy_config_init + self._symbol_shape = symbol_shape + + @property + def phy_config_shape(self): + """Shape of the PHY configuration word. + + Returns + ------- + :ref:`shape-like ` + """ + return self._phy_config_shape + + @property + def phy_config_init(self): + """Initial value of the PHY configuration word. + + Returns + ------- + :class:`int` + """ + return self._phy_config_init + + @property + def symbol_shape(self): + """Shape of a symbol. + + Returns + ------- + :ref:`shape-like ` + """ + return self._symbol_shape + + def elaborate(self, platform): + m = Module() + m.submodules.bridge = self._bridge + + connect(m, flipped(self.bus), self._bridge.bus) + + m.d.comb += [ + self.phy.rst.eq(~self._config.f.enable.data), + + self._phy_config.f.w_en.eq(self.phy.rst), + self.phy.config.eq(self._phy_config.f.data), + + self._status.f.ready.r_data.eq(self.phy.symbols.ready), + self.phy.symbols.payload.eq(self._data.f.w_data), + self.phy.symbols.valid.eq(self._data.f.w_stb), + ] + + return m + + +class Peripheral(wiring.Component): + """UART transceiver peripheral. + + This peripheral is composed of two subordinate peripherals. A :class:`RxPeripheral` occupies + the bottom half of the address space, and a :class:`TxPeripheral` occupies the top half. + Both subordinates have the same parameters as this peripheral, except ``name`` (which becomes + ``"rx"`` or ``"tx"`` respectively) and ``addr_width`` (which becomes ``addr_width - 1``). + + Parameters + ---------- + addr_width : :class:`int` + CSR bus address width. + data_width : :class:`int` + CSR bus data width. + name : :class:`str` + CSR bus window name. Optional. Defaults to ``None``. + phy_config_shape : :ref:`shape-like ` + Shape of the PHY configuration word. Optional. Defaults to ``unsigned(16)``. + phy_config_init : :class:`int` + Initial value of the PHY configuration word. Optional. Defaults to ``0``. + symbol_shape : :ref:`shape-like ` + Shape of a symbol. Optional. Defaults to ``unsigned(8)``. + + Attributes + ---------- + bus : :class:`csr.Interface` + CSR bus interface providing access to registers. + rx : :class:`wiring.PureInterface` of :class:`RxPhySignature` + Interface between the peripheral and its PHY receiver. + tx : :class:`wiring.PureInterface` of :class:`TxPhySignature` + Interface between the peripheral and its PHY transmitter. + + Raises + ------ + :exc:`TypeError` + If ``addr_width`` is not a positive integer. + """ + def __init__(self, *, addr_width, data_width=8, name=None, phy_config_shape=unsigned(16), + phy_config_init=0, symbol_shape=unsigned(8)): + if not isinstance(addr_width, int) or addr_width <= 0: + raise TypeError(f"Address width must be a positive integer, not {addr_width!r}") + + self._rx = RxPeripheral(addr_width=addr_width - 1, data_width=data_width, name="rx", + phy_config_shape=phy_config_shape, phy_config_init=phy_config_init, + symbol_shape=symbol_shape) + self._tx = TxPeripheral(addr_width=addr_width - 1, data_width=data_width, name="tx", + phy_config_shape=phy_config_shape, phy_config_init=phy_config_init, + symbol_shape=symbol_shape) + + self._decoder = csr.Decoder(addr_width=addr_width, data_width=data_width, name=name) + self._decoder.add(self._rx.bus) + self._decoder.add(self._tx.bus) + + super().__init__({ + "bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)), + "rx": Out(RxPhySignature(phy_config_shape, symbol_shape)), + "tx": Out(TxPhySignature(phy_config_shape, symbol_shape)), + }) + self.bus.memory_map = self._decoder.bus.memory_map + + self._phy_config_shape = phy_config_shape + self._phy_config_init = phy_config_init + self._symbol_shape = symbol_shape + + @property + def phy_config_shape(self): + """Shape of the PHY configuration word. + + Returns + ------- + :ref:`shape-like ` + """ + return self._phy_config_shape + + @property + def phy_config_init(self): + """Initial value of the PHY configuration word. + + Returns + ------- + :class:`int` + """ + return self._phy_config_init + + @property + def symbol_shape(self): + """Shape of a symbol. + + Returns + ------- + :ref:`shape-like ` + """ + return self._symbol_shape + + def elaborate(self, platform): + m = Module() + m.submodules.decoder = self._decoder + m.submodules.rx = self._rx + m.submodules.tx = self._tx + + connect(m, flipped(self.bus), self._decoder.bus) + connect(m, self._rx.phy, flipped(self.rx)) + connect(m, self._tx.phy, flipped(self.tx)) + + return m diff --git a/tests/test_uart.py b/tests/test_uart.py new file mode 100644 index 0000000..26f76ec --- /dev/null +++ b/tests/test_uart.py @@ -0,0 +1,463 @@ +# amaranth: UnusedElaboratable=no +import unittest +from amaranth import * +from amaranth.lib import wiring +from amaranth.lib.wiring import In, Out, connect +from amaranth.lib.fifo import SyncFIFO +from amaranth.sim import * + +from amaranth_soc import uart + + +def _csr_access(self, dut, addr, r_stb=0, r_data=0, w_stb=0, w_data=0): + yield dut.bus.addr.eq(addr) + yield dut.bus.r_stb.eq(r_stb) + yield dut.bus.w_stb.eq(w_stb) + yield dut.bus.w_data.eq(w_data) + yield Tick() + if r_stb: + self.assertEqual((yield dut.bus.r_data), r_data) + yield dut.bus.r_stb.eq(0) + yield dut.bus.w_stb.eq(0) + + +class RxPeripheralTestCase(unittest.TestCase): + def test_init(self): + dut_1 = uart.RxPeripheral(addr_width=3, data_width=8) + self.assertEqual(dut_1.phy_config_shape, unsigned(16)) + self.assertEqual(dut_1.phy_config_init, 0) + self.assertEqual(dut_1.symbol_shape, unsigned(8)) + self.assertEqual(dut_1.bus.addr_width, 3) + self.assertEqual(dut_1.bus.data_width, 8) + dut_2 = uart.RxPeripheral(addr_width=8, data_width=16, + phy_config_shape=unsigned(32), phy_config_init=0xa5a5a5a5, + symbol_shape=unsigned(16)) + self.assertEqual(dut_2.phy_config_shape, unsigned(32)) + self.assertEqual(dut_2.phy_config_init, 0xa5a5a5a5) + self.assertEqual(dut_2.symbol_shape, unsigned(16)) + self.assertEqual(dut_2.bus.addr_width, 8) + self.assertEqual(dut_2.bus.data_width, 16) + + def test_sim(self): + dut = uart.RxPeripheral(addr_width=3, data_width=8, phy_config_init=0xa455) + + config_addr = 0x0 + phy_config_addr = 0x2 + status_addr = 0x4 + data_addr = 0x5 + + def testbench(): + # PHY disabled (initial state) ======================================================== + + self.assertEqual((yield dut.phy.rst), 1) + + # - read Config (enable=0): + yield from _csr_access(self, dut, config_addr, r_stb=1, r_data=0) + + self.assertEqual((yield dut.phy.config), 0xa455) + + # - read PhyConfig (=0xa455) and write 0x0001: + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x55, w_stb=1, w_data=0x01) + self.assertEqual((yield dut.phy.config), 0xa455) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0xa4, w_stb=1, w_data=0x00) + self.assertEqual((yield dut.phy.config), 0xa455) + yield Tick() + self.assertEqual((yield dut.phy.config), 0x0001) + + # - read PhyConfig (=0x0001): + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x01) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0x00) + + # - read Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0b000) + + # PHY disabled -> enabled ============================================================= + + self.assertEqual((yield dut.phy.rst), 1) + + # - read Config (enable=0) and write 1: + yield from _csr_access(self, dut, config_addr, r_stb=1, r_data=0, w_stb=1, w_data=1) + self.assertEqual((yield dut.phy.rst), 1) + yield Tick() + self.assertEqual((yield dut.phy.rst), 0) + + # PHY enabled ========================================================================= + + self.assertEqual((yield dut.phy.config), 0x0001) + + # - read PhyConfig (=0x0001) and write 0xa455: + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x01, w_stb=1, w_data=0x55) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0x00, w_stb=1, w_data=0xa4) + yield Tick() + self.assertEqual((yield dut.phy.config), 0x0001) + + # - read PhyConfig (=0x0001): + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x01) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0x00) + yield Tick() + + self.assertEqual((yield dut.phy.symbols.ready), 0) + + yield dut.phy.symbols.payload.eq(ord("a")) + yield dut.phy.symbols.valid.eq(1) + + # - read Status (ready=1, overflow=0, error=0): + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0b001) + + # - read Data (="a"): + yield dut.bus.addr.eq(data_addr) + yield dut.bus.r_stb.eq(1) + yield Tick() + self.assertEqual((yield dut.bus.r_data), ord("a")) + self.assertEqual((yield dut.phy.symbols.ready), 1) + yield dut.bus.r_stb.eq(0) + yield Tick() + self.assertEqual((yield dut.phy.symbols.ready), 0) + yield dut.phy.symbols.valid.eq(0) + + # - read Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0b000) + + yield dut.phy.error.eq(1) + yield dut.phy.overflow.eq(1) + yield Tick() + yield dut.phy.error.eq(0) + yield dut.phy.overflow.eq(0) + + # - read Status (ready=0, overflow=1, error=1): + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0b110) + + yield dut.phy.symbols.payload.eq(ord("b")) + yield dut.phy.symbols.valid.eq(1) + + # - read Status (ready=1, overflow=1, error=1): + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0b111) + + # PHY enabled -> disabled ============================================================= + + self.assertEqual((yield dut.phy.rst), 0) + + # - read Config (enable=1) and write 0: + yield from _csr_access(self, dut, config_addr, r_stb=1, r_data=1, w_stb=1, w_data=0) + self.assertEqual((yield dut.phy.rst), 0) + yield Tick() + self.assertEqual((yield dut.phy.rst), 1) + + yield dut.phy.symbols.valid.eq(0) + + # - read Status (ready=0, overflow=1, error=1) and write 0b110: + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0b110, w_stb=1, w_data=0b110) + yield Tick() + + # - read Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0b000) + + sim = Simulator(dut) + sim.add_clock(1e-6) + sim.add_testbench(testbench) + with sim.write_vcd(vcd_file=open("test.vcd", "w")): + sim.run() + + +class TxPeripheralTestCase(unittest.TestCase): + def test_init(self): + dut_1 = uart.TxPeripheral(addr_width=3, data_width=8) + self.assertEqual(dut_1.phy_config_shape, unsigned(16)) + self.assertEqual(dut_1.phy_config_init, 0) + self.assertEqual(dut_1.symbol_shape, unsigned(8)) + self.assertEqual(dut_1.bus.addr_width, 3) + self.assertEqual(dut_1.bus.data_width, 8) + dut_2 = uart.TxPeripheral(addr_width=8, data_width=16, + phy_config_shape=unsigned(32), phy_config_init=0xa5a5a5a5, + symbol_shape=unsigned(16)) + self.assertEqual(dut_2.phy_config_shape, unsigned(32)) + self.assertEqual(dut_2.phy_config_init, 0xa5a5a5a5) + self.assertEqual(dut_2.symbol_shape, unsigned(16)) + self.assertEqual(dut_2.bus.addr_width, 8) + self.assertEqual(dut_2.bus.data_width, 16) + + def test_sim(self): + dut = uart.TxPeripheral(addr_width=3, data_width=8, phy_config_init=0xa455) + + config_addr = 0x0 + phy_config_addr = 0x2 + status_addr = 0x4 + data_addr = 0x5 + + def testbench(): + # PHY disabled (initial state) ======================================================== + + self.assertEqual((yield dut.phy.rst), 1) + yield dut.phy.symbols.ready.eq(0) + + # - read Config (enable=0): + yield from _csr_access(self, dut, config_addr, r_stb=1, r_data=0) + + self.assertEqual((yield dut.phy.config), 0xa455) + + # - read PhyConfig (=0xa455) and write 0x0001: + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x55, w_stb=1, w_data=0x01) + self.assertEqual((yield dut.phy.config), 0xa455) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0xa4, w_stb=1, w_data=0x00) + self.assertEqual((yield dut.phy.config), 0xa455) + yield Tick() + self.assertEqual((yield dut.phy.config), 0x0001) + + # - read PhyConfig (=0x0001): + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x01) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0x00) + + # - read Status (ready=0): + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0) + + # PHY disabled -> enabled ============================================================= + + self.assertEqual((yield dut.phy.rst), 1) + + # - read Config (enable=0) and write 1: + yield from _csr_access(self, dut, config_addr, r_stb=1, r_data=0, w_stb=1, w_data=1) + self.assertEqual((yield dut.phy.rst), 1) + yield Tick() + self.assertEqual((yield dut.phy.rst), 0) + + # PHY enabled ========================================================================= + + self.assertEqual((yield dut.phy.config), 0x0001) + + # - read PhyConfig (=0x0001) and write 0xa455: + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x01, w_stb=1, w_data=0x55) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0x00, w_stb=1, w_data=0xa4) + yield Tick() + self.assertEqual((yield dut.phy.config), 0x0001) + + # - read PhyConfig (=0x0001): + yield from _csr_access(self, dut, phy_config_addr+0, r_stb=1, r_data=0x01) + yield from _csr_access(self, dut, phy_config_addr+1, r_stb=1, r_data=0x00) + yield Tick() + + # - read Status (ready=0) + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0) + + yield dut.phy.symbols.ready.eq(1) + + # - read Status (ready=1) + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=1) + + # - write "a" to Data: + yield dut.bus.addr.eq(data_addr) + yield dut.bus.w_stb.eq(1) + yield dut.bus.w_data.eq(ord("a")) + yield Tick() + self.assertEqual((yield dut.phy.symbols.valid), 1) + self.assertEqual((yield dut.phy.symbols.payload), ord("a")) + yield dut.bus.w_stb.eq(0) + yield Tick() + self.assertEqual((yield dut.phy.symbols.valid), 0) + + yield dut.phy.symbols.ready.eq(0) + + # - read Status (ready=0) + yield from _csr_access(self, dut, status_addr, r_stb=1, r_data=0) + + # PHY enabled -> disabled ============================================================= + + self.assertEqual((yield dut.phy.rst), 0) + + # - read Config (enable=1) and write 0: + yield from _csr_access(self, dut, config_addr, r_stb=1, r_data=1, w_stb=1, w_data=0) + self.assertEqual((yield dut.phy.rst), 0) + yield Tick() + self.assertEqual((yield dut.phy.rst), 1) + + sim = Simulator(dut) + sim.add_clock(1e-6) + sim.add_testbench(testbench) + with sim.write_vcd(vcd_file=open("test.vcd", "w")): + sim.run() + + +class _LoopbackPHY(wiring.Component): + rx: In(uart.RxPhySignature(phy_config_shape=unsigned(16), symbol_shape=unsigned(8))) + tx: In(uart.TxPhySignature(phy_config_shape=unsigned(16), symbol_shape=unsigned(8))) + + def elaborate(self, platform): + m = Module() + + fifo = SyncFIFO(width=8, depth=4) + m.submodules.fifo = ResetInserter(self.rx.rst | self.tx.rst)(fifo) + + m.d.comb += [ + self.tx.symbols.ready.eq(fifo.w_rdy & ~self.tx.rst), + fifo.w_en.eq(self.tx.symbols.valid), + fifo.w_data.eq(self.tx.symbols.payload), + + self.rx.symbols.valid.eq(fifo.r_rdy), + self.rx.symbols.payload.eq(fifo.r_data), + fifo.r_en.eq(self.rx.symbols.ready), + + self.rx.overflow.eq(fifo.w_en & ~fifo.w_rdy), + ] + + return m + + +class PeripheralTestCase(unittest.TestCase): + def test_init(self): + dut_1 = uart.Peripheral(addr_width=4, data_width=8) + self.assertEqual(dut_1.phy_config_shape, unsigned(16)) + self.assertEqual(dut_1.phy_config_init, 0) + self.assertEqual(dut_1.symbol_shape, unsigned(8)) + self.assertEqual(dut_1.bus.addr_width, 4) + self.assertEqual(dut_1.bus.data_width, 8) + dut_2 = uart.Peripheral(addr_width=8, data_width=16, + phy_config_shape=unsigned(32), phy_config_init=0xa5a5a5a5, + symbol_shape=unsigned(16)) + self.assertEqual(dut_2.phy_config_shape, unsigned(32)) + self.assertEqual(dut_2.phy_config_init, 0xa5a5a5a5) + self.assertEqual(dut_2.symbol_shape, unsigned(16)) + self.assertEqual(dut_2.bus.addr_width, 8) + self.assertEqual(dut_2.bus.data_width, 16) + + def test_init_wrong_addr_width(self): + with self.assertRaisesRegex(TypeError, + r"Address width must be a positive integer, not 'foo'"): + uart.Peripheral(addr_width="foo", data_width=8) + with self.assertRaisesRegex(TypeError, + r"Address width must be a positive integer, not 0"): + uart.Peripheral(addr_width=0, data_width=8) + + def test_sim(self): + dut = uart.Peripheral(addr_width=5, data_width=8) + phy = _LoopbackPHY() + + m = Module() + m.submodules.dut = dut + m.submodules.phy = phy + + connect(m, dut.rx, phy.rx) + connect(m, dut.tx, phy.tx) + + rx_config_addr = 0x00 + rx_phy_config_addr = 0x02 + rx_status_addr = 0x04 + rx_data_addr = 0x05 + + tx_config_addr = 0x10 + tx_phy_config_addr = 0x12 + tx_status_addr = 0x14 + tx_data_addr = 0x15 + + def testbench(): + # PHY disabled ======================================================================== + + # - read rx.Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b000) + + # - read tx.Status (ready=0): + yield from _csr_access(self, dut, tx_status_addr, r_stb=1, r_data=0) + + # - write "x" to tx.Data: + yield from _csr_access(self, dut, tx_data_addr, w_stb=1, w_data=ord("x")) + yield Tick() + + # - read rx.Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b000) + + # PHY disabled -> enabled ============================================================= + + # - read rx.Config (enable=0) and write 1: + yield from _csr_access(self, dut, rx_config_addr, r_stb=1, r_data=0, w_stb=1, w_data=1) + yield Tick() + + # - read tx.Config (enable=0) and write 1: + yield from _csr_access(self, dut, tx_config_addr, r_stb=1, r_data=0, w_stb=1, w_data=1) + yield Tick() + + # - read rx.Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b000) + + # PHY enabled ========================================================================= + + for c in "abcd": + # - read tx.Status (ready=1): + yield from _csr_access(self, dut, tx_status_addr, r_stb=1, r_data=1) + + # - write c to tx.Data: + yield from _csr_access(self, dut, tx_data_addr, w_stb=1, w_data=ord(c)) + yield Tick() + + # - read rx.Status (ready=1, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b001) + + # - read tx.Status (ready=0): + yield from _csr_access(self, dut, tx_status_addr, r_stb=1, r_data=0) + + # - write "e" to tx.Data: + yield from _csr_access(self, dut, tx_data_addr, w_stb=1, w_data=ord("e")) + yield Tick() + + # - read rx.Status (ready=1, overflow=1, error=0) and write 0b010: + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b011, w_stb=1, w_data=0b010) + yield Tick() + + for c in "abcd": + # - read rx.Status (ready=1, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b001) + + # - read rx.Data (=c) + yield from _csr_access(self, dut, rx_data_addr, r_stb=1, r_data=ord(c)) + + # - read rx.Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b000) + + # PHY enabled -> disabled ============================================================= + + for c in "efgh": + # - read tx.Status (ready=1): + yield from _csr_access(self, dut, tx_status_addr, r_stb=1, r_data=1) + + # - write c to tx.Data: + yield from _csr_access(self, dut, tx_data_addr, w_stb=1, w_data=ord(c)) + yield Tick() + + # - read tx.Status (ready=0): + yield from _csr_access(self, dut, tx_status_addr, r_stb=1, r_data=0) + + # - read rx.Status (ready=1, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b001) + + # - read rx.Config (enable=1) and write 0: + yield from _csr_access(self, dut, rx_config_addr, r_stb=1, r_data=1, w_stb=1, w_data=0) + yield Tick() + + # - read tx.Config (enable=1) and write 0: + yield from _csr_access(self, dut, tx_config_addr, r_stb=1, r_data=1, w_stb=1, w_data=0) + yield Tick() + + # - read tx.Status (ready=0): + yield from _csr_access(self, dut, tx_status_addr, r_stb=1, r_data=0) + + # - read rx.Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b000) + + # PHY disabled -> enabled ============================================================= + + # - read rx.Config (enable=0) and write 1: + yield from _csr_access(self, dut, rx_config_addr, r_stb=1, r_data=0, w_stb=1, w_data=1) + yield Tick() + + # - read tx.Config (enable=0) and write 1: + yield from _csr_access(self, dut, tx_config_addr, r_stb=1, r_data=0, w_stb=1, w_data=1) + yield Tick() + + # - read tx.Status (ready=1): + yield from _csr_access(self, dut, tx_status_addr, r_stb=1, r_data=1) + + # - read rx.Status (ready=0, overflow=0, error=0): + yield from _csr_access(self, dut, rx_status_addr, r_stb=1, r_data=0b000) + + sim = Simulator(m) + sim.add_clock(1e-6) + sim.add_testbench(testbench) + with sim.write_vcd(vcd_file=open("test.vcd", "w")): + sim.run()